aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.travis.yml82
-rw-r--r--Example/Auth/ApiTests/FirebaseAuthApiTests.m16
-rw-r--r--Example/Core/Tests/FIRAppTest.m130
-rw-r--r--Example/Core/Tests/FIROptionsTest.m10
-rw-r--r--Example/Firebase.xcodeproj/project.pbxproj40
-rw-r--r--Example/Messaging/Tests/FIRMessagingTest.m40
-rw-r--r--Firebase/Auth/CHANGELOG.md7
-rw-r--r--Firebase/Auth/Source/FIRAuthErrorUtils.m2
-rw-r--r--Firebase/Auth/Source/Public/FIRAuth.h63
-rw-r--r--Firebase/Core/FIRApp.m85
-rw-r--r--Firebase/Core/Private/FIRAppInternal.h28
-rw-r--r--Firebase/Database/Api/Private/FIRDataSnapshot_Private.h1
-rw-r--r--Firebase/Messaging/FIRMessaging+FIRApp.m1
-rw-r--r--Firebase/Messaging/FIRMessaging.m7
-rw-r--r--Firebase/Messaging/FIRMessaging_Private.h4
-rw-r--r--FirebaseMessaging.podspec1
-rw-r--r--Firestore/CHANGELOG.md7
-rw-r--r--Firestore/Example/App/iOS/Base.lproj/LaunchScreen.storyboard (renamed from Firestore/Example/Firestore/Base.lproj/LaunchScreen.storyboard)0
-rw-r--r--Firestore/Example/App/iOS/Base.lproj/Main.storyboard (renamed from Firestore/Example/Firestore/Base.lproj/Main.storyboard)0
-rw-r--r--Firestore/Example/App/iOS/FIRAppDelegate.h (renamed from Firestore/Example/Firestore/FIRAppDelegate.h)0
-rw-r--r--Firestore/Example/App/iOS/FIRAppDelegate.m (renamed from Firestore/Example/Firestore/FIRAppDelegate.m)0
-rw-r--r--Firestore/Example/App/iOS/FIRViewController.h (renamed from Firestore/Example/Firestore/FIRViewController.h)0
-rw-r--r--Firestore/Example/App/iOS/FIRViewController.m (renamed from Firestore/Example/Firestore/FIRViewController.m)0
-rw-r--r--Firestore/Example/App/iOS/Firestore-Info.plist (renamed from Firestore/Example/Firestore/Firestore-Info.plist)0
-rw-r--r--Firestore/Example/App/iOS/Images.xcassets/AppIcon.appiconset/Contents.json (renamed from Firestore/Example/Firestore/Images.xcassets/AppIcon.appiconset/Contents.json)0
-rw-r--r--Firestore/Example/App/iOS/en.lproj/InfoPlist.strings (renamed from Firestore/Example/Firestore/en.lproj/InfoPlist.strings)0
-rw-r--r--Firestore/Example/App/iOS/main.m (renamed from Firestore/Example/Firestore/main.m)0
-rw-r--r--Firestore/Example/Firestore.xcodeproj/project.pbxproj486
-rw-r--r--Firestore/Example/Podfile25
-rw-r--r--Firestore/Example/Tests/Core/FSTQueryListenerTests.mm57
-rw-r--r--Firestore/Example/Tests/Core/FSTViewTests.mm61
-rw-r--r--Firestore/Example/Tests/GoogleTest/GoogleTest.podspec17
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRArrayTransformTests.mm52
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRCursorTests.mm23
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm149
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm73
-rw-r--r--Firestore/Example/Tests/Integration/API/FIRValidationTests.mm4
-rw-r--r--Firestore/Example/Tests/Integration/FSTDatastoreTests.mm1
-rw-r--r--Firestore/Example/Tests/Integration/FSTStreamTests.mm6
-rw-r--r--Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm6
-rw-r--r--Firestore/Example/Tests/Local/FSTLocalStoreTests.mm32
-rw-r--r--Firestore/Example/Tests/Local/FSTQueryCacheTests.mm32
-rw-r--r--Firestore/Example/Tests/Model/FSTDocumentTests.mm8
-rw-r--r--Firestore/Example/Tests/Model/FSTMutationTests.mm30
-rw-r--r--Firestore/Example/Tests/Model/field_transform_test.mm (renamed from Firestore/core/test/firebase/firestore/model/field_transform_test.cc)5
-rw-r--r--Firestore/Example/Tests/Model/transform_operations_test.mm (renamed from Firestore/core/test/firebase/firestore/model/transform_operations_test.cc)16
-rw-r--r--Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm192
-rw-r--r--Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm10
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTMockDatastore.h10
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm20
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSpecTests.mm32
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h7
-rw-r--r--Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm6
-rw-r--r--Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm46
-rw-r--r--Firestore/Example/Tests/Util/FSTEventAccumulator.h12
-rw-r--r--Firestore/Example/Tests/Util/FSTEventAccumulator.mm28
-rw-r--r--Firestore/Example/Tests/Util/FSTHelpers.h8
-rw-r--r--Firestore/Example/Tests/Util/FSTHelpers.mm40
-rw-r--r--Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm8
-rw-r--r--Firestore/Source/API/FIRDocumentReference.mm24
-rw-r--r--Firestore/Source/API/FIRFieldPath.mm3
-rw-r--r--Firestore/Source/API/FIRFirestore.mm9
-rw-r--r--Firestore/Source/API/FIRQuery.mm8
-rw-r--r--Firestore/Source/API/FIRTransaction.mm15
-rw-r--r--Firestore/Source/API/FIRWriteBatch.mm17
-rw-r--r--Firestore/Source/API/FSTUserDataConverter.h3
-rw-r--r--Firestore/Source/API/FSTUserDataConverter.mm42
-rw-r--r--Firestore/Source/Core/FSTFirestoreClient.h19
-rw-r--r--Firestore/Source/Core/FSTFirestoreClient.mm67
-rw-r--r--Firestore/Source/Core/FSTQuery.mm5
-rw-r--r--Firestore/Source/Core/FSTSnapshotVersion.h43
-rw-r--r--Firestore/Source/Core/FSTSnapshotVersion.mm80
-rw-r--r--Firestore/Source/Core/FSTSyncEngine.mm25
-rw-r--r--Firestore/Source/Core/FSTTransaction.mm26
-rw-r--r--Firestore/Source/Core/FSTView.h11
-rw-r--r--Firestore/Source/Core/FSTView.mm100
-rw-r--r--Firestore/Source/Local/FSTLevelDB.mm4
-rw-r--r--Firestore/Source/Local/FSTLevelDBMutationQueue.mm1
-rw-r--r--Firestore/Source/Local/FSTLevelDBQueryCache.mm41
-rw-r--r--Firestore/Source/Local/FSTLocalDocumentsView.h5
-rw-r--r--Firestore/Source/Local/FSTLocalDocumentsView.mm16
-rw-r--r--Firestore/Source/Local/FSTLocalSerializer.h11
-rw-r--r--Firestore/Source/Local/FSTLocalSerializer.mm32
-rw-r--r--Firestore/Source/Local/FSTLocalStore.h8
-rw-r--r--Firestore/Source/Local/FSTLocalStore.mm133
-rw-r--r--Firestore/Source/Local/FSTLocalViewChanges.h11
-rw-r--r--Firestore/Source/Local/FSTLocalViewChanges.mm50
-rw-r--r--Firestore/Source/Local/FSTMemoryMutationQueue.h5
-rw-r--r--Firestore/Source/Local/FSTMemoryMutationQueue.mm12
-rw-r--r--Firestore/Source/Local/FSTMemoryPersistence.mm8
-rw-r--r--Firestore/Source/Local/FSTMemoryQueryCache.h7
-rw-r--r--Firestore/Source/Local/FSTMemoryQueryCache.mm44
-rw-r--r--Firestore/Source/Local/FSTPersistence.h57
-rw-r--r--Firestore/Source/Local/FSTQueryCache.h17
-rw-r--r--Firestore/Source/Local/FSTQueryData.h22
-rw-r--r--Firestore/Source/Local/FSTQueryData.mm40
-rw-r--r--Firestore/Source/Local/FSTReferenceSet.h10
-rw-r--r--Firestore/Source/Local/FSTReferenceSet.mm20
-rw-r--r--Firestore/Source/Model/FSTDocument.h9
-rw-r--r--Firestore/Source/Model/FSTDocument.mm40
-rw-r--r--Firestore/Source/Model/FSTDocumentKey.mm3
-rw-r--r--Firestore/Source/Model/FSTDocumentKeySet.mm31
-rw-r--r--Firestore/Source/Model/FSTDocumentVersionDictionary.h40
-rw-r--r--Firestore/Source/Model/FSTDocumentVersionDictionary.mm37
-rw-r--r--Firestore/Source/Model/FSTMutation.h8
-rw-r--r--Firestore/Source/Model/FSTMutation.mm112
-rw-r--r--Firestore/Source/Model/FSTMutationBatch.h28
-rw-r--r--Firestore/Source/Model/FSTMutationBatch.mm53
-rw-r--r--Firestore/Source/Public/FIRDocumentReference.h39
-rw-r--r--Firestore/Source/Public/FIRTransaction.h24
-rw-r--r--Firestore/Source/Public/FIRWriteBatch.h23
-rw-r--r--Firestore/Source/Remote/FSTDatastore.h1
-rw-r--r--Firestore/Source/Remote/FSTRemoteEvent.h65
-rw-r--r--Firestore/Source/Remote/FSTRemoteEvent.mm312
-rw-r--r--Firestore/Source/Remote/FSTRemoteStore.h1
-rw-r--r--Firestore/Source/Remote/FSTRemoteStore.mm23
-rw-r--r--Firestore/Source/Remote/FSTSerializerBeta.h19
-rw-r--r--Firestore/Source/Remote/FSTSerializerBeta.mm70
-rw-r--r--Firestore/Source/Remote/FSTStream.h7
-rw-r--r--Firestore/Source/Remote/FSTStream.mm6
-rw-r--r--Firestore/Source/Remote/FSTWatchChange.h1
-rw-r--r--Firestore/Source/Util/FSTAsyncQueryListener.h7
-rw-r--r--Firestore/Source/Util/FSTAsyncQueryListener.mm14
-rw-r--r--Firestore/Source/Util/FSTDispatchQueue.mm277
-rw-r--r--Firestore/core/CMakeLists.txt1
-rw-r--r--Firestore/core/src/firebase/firestore/CMakeLists.txt1
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt1
-rw-r--r--Firestore/core/src/firebase/firestore/immutable/sorted_set.h157
-rw-r--r--Firestore/core/src/firebase/firestore/model/base_path.h14
-rw-r--r--Firestore/core/src/firebase/firestore/model/database_id.h6
-rw-r--r--Firestore/core/src/firebase/firestore/model/document_key.h26
-rw-r--r--Firestore/core/src/firebase/firestore/model/document_key_set.h (renamed from Firestore/Source/Model/FSTDocumentKeySet.h)27
-rw-r--r--Firestore/core/src/firebase/firestore/model/field_mask.h23
-rw-r--r--Firestore/core/src/firebase/firestore/model/field_transform.h5
-rw-r--r--Firestore/core/src/firebase/firestore/model/precondition.h1
-rw-r--r--Firestore/core/src/firebase/firestore/model/snapshot_version.h30
-rw-r--r--Firestore/core/src/firebase/firestore/model/transform_operations.h94
-rw-r--r--Firestore/core/src/firebase/firestore/nanopb/CMakeLists.txt27
-rw-r--r--Firestore/core/src/firebase/firestore/nanopb/reader.cc141
-rw-r--r--Firestore/core/src/firebase/firestore/nanopb/reader.h175
-rw-r--r--Firestore/core/src/firebase/firestore/nanopb/tag.h43
-rw-r--r--Firestore/core/src/firebase/firestore/nanopb/writer.cc165
-rw-r--r--Firestore/core/src/firebase/firestore/nanopb/writer.h140
-rw-r--r--Firestore/core/src/firebase/firestore/remote/CMakeLists.txt2
-rw-r--r--Firestore/core/src/firebase/firestore/remote/serializer.cc769
-rw-r--r--Firestore/core/src/firebase/firestore/remote/serializer.h113
-rw-r--r--Firestore/core/src/firebase/firestore/timestamp_internal.h49
-rw-r--r--Firestore/core/src/firebase/firestore/util/CMakeLists.txt59
-rw-r--r--Firestore/core/src/firebase/firestore/util/async_queue.cc142
-rw-r--r--Firestore/core/src/firebase/firestore/util/async_queue.h164
-rw-r--r--Firestore/core/src/firebase/firestore/util/executor.h129
-rw-r--r--Firestore/core/src/firebase/firestore/util/executor_libdispatch.h91
-rw-r--r--Firestore/core/src/firebase/firestore/util/executor_libdispatch.mm284
-rw-r--r--Firestore/core/src/firebase/firestore/util/executor_std.cc155
-rw-r--r--Firestore/core/src/firebase/firestore/util/executor_std.h280
-rw-r--r--Firestore/core/src/firebase/firestore/util/hashing.h151
-rw-r--r--Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt1
-rw-r--r--Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc4
-rw-r--r--Firestore/core/test/firebase/firestore/immutable/sorted_set_test.cc182
-rw-r--r--Firestore/core/test/firebase/firestore/immutable/testing.h50
-rw-r--r--Firestore/core/test/firebase/firestore/model/CMakeLists.txt2
-rw-r--r--Firestore/core/test/firebase/firestore/model/document_key_test.cc23
-rw-r--r--Firestore/core/test/firebase/firestore/remote/serializer_test.cc434
-rw-r--r--Firestore/core/test/firebase/firestore/util/CMakeLists.txt55
-rw-r--r--Firestore/core/test/firebase/firestore/util/async_queue_libdispatch_test.mm87
-rw-r--r--Firestore/core/test/firebase/firestore/util/async_queue_std_test.cc41
-rw-r--r--Firestore/core/test/firebase/firestore/util/async_queue_test.cc188
-rw-r--r--Firestore/core/test/firebase/firestore/util/async_queue_test.h47
-rw-r--r--Firestore/core/test/firebase/firestore/util/async_tests_util.h90
-rw-r--r--Firestore/core/test/firebase/firestore/util/executor_libdispatch_test.mm74
-rw-r--r--Firestore/core/test/firebase/firestore/util/executor_std_test.cc244
-rw-r--r--Firestore/core/test/firebase/firestore/util/executor_test.cc218
-rw-r--r--Firestore/core/test/firebase/firestore/util/executor_test.h46
-rw-r--r--Firestore/core/test/firebase/firestore/util/hashing_test.cc105
-rw-r--r--Functions/Example/FirebaseFunctions.xcodeproj/project.pbxproj209
-rw-r--r--Functions/README.md16
-rw-r--r--Gemfile.lock46
-rwxr-xr-xscripts/build.sh13
-rwxr-xr-xscripts/check_test_inclusion.py106
-rwxr-xr-xscripts/if_changed.sh3
-rwxr-xr-xscripts/if_cron.sh25
-rwxr-xr-xscripts/install_prereqs.sh34
-rwxr-xr-xscripts/lint.sh2
-rwxr-xr-xscripts/pod_install.sh54
184 files changed, 7490 insertions, 2701 deletions
diff --git a/.travis.yml b/.travis.yml
index fff1ec4..7cec178 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,6 +18,7 @@ jobs:
- ./scripts/check_whitespace.sh
- ./scripts/check_copyright.sh
- ./scripts/check_no_module_imports.sh
+ - ./scripts/check_test_inclusion.py
- ./scripts/style.sh test-only $TRAVIS_COMMIT_RANGE
# Google C++ style compliance
- ./scripts/lint.sh $TRAVIS_COMMIT_RANGE
@@ -27,37 +28,75 @@ jobs:
# Primary platforms
+ # Run unit tests
- stage: test
env:
- - PROJECT=Firebase PLATFORM=iOS
+ - PROJECT=Firebase PLATFORM=iOS METHOD=xcodebuild
before_install:
- # Add next line back with updated DeviceUDID for xcode9.1 if stability
- # issues with simulator:
- # - open -a "simulator" --args -CurrentDeviceUDID ABBD7191-486B-462F-80B4-AE08C5820DA1
+ - npm install ios-sim -g
+ - ios-sim start --devicetypeid "com.apple.CoreSimulator.SimDeviceType.iPhone-8-Plus, 11.3"
- ./scripts/if_changed.sh ./scripts/install_prereqs.sh
- - ./scripts/if_changed.sh ./scripts/pod_install.sh
script:
- ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM
- # Disabled because of url validation failures on April 20, 2018
- #- ./scripts/if_changed.sh bundle exec pod lib lint FirebaseCore.podspec
-
- # TODO - Uncomment subsequent lines once FirebaseCore source repo is in public Specs repo
- # - bundle exec pod lib lint FirebaseAuth.podspec
- # - bundle exec pod lib lint FirebaseDatabase.podspec
- # - bundle exec pod lib lint FirebaseMessaging.podspec
- # - bundle exec pod lib lint FirebaseStorage.podspec
- # - bundle exec pod lib lint FirebaseFirestore.podspec
-
- stage: test
env:
- PROJECT=Firestore PLATFORM=iOS METHOD=xcodebuild
before_install:
- ./scripts/if_changed.sh ./scripts/install_prereqs.sh
- - ./scripts/if_changed.sh ./scripts/pod_install.sh
script:
- ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM $METHOD
+ # TODO - add a script to make the lint checks more DRY.
+ # pod lib lint to check build and warnings for dynamic framework build (use_frameworks!)
+ - stage: test
+ env:
+ - PROJECT=Firebase PLATFORM=iOS METHOD=pod-lib-lint
+ before_install:
+ - ./scripts/if_changed.sh ./scripts/install_prereqs.sh
+ script:
+ - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseCore.podspec
+ - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseAuth.podspec
+ - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseDatabase.podspec
+# - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseMessaging.podspec
+ - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseStorage.podspec
+ - ./scripts/if_changed.sh bundle exec pod lib lint FirebaseFunctions.podspec
+
+ - stage: test
+ env:
+ - PROJECT=Firestore PLATFORM=iOS METHOD=pod-lib-lint
+ before_install:
+ - ./scripts/if_changed.sh ./scripts/install_prereqs.sh
+ script:
+ # Eliminate the one warning from BoringSSL when CocoaPods 1.6.0 is available.
+ # The travis_wait is necessary because the command takes more than 10 minutes.
+ - travis_wait ./scripts/if_changed.sh bundle exec pod lib lint FirebaseFirestore.podspec --allow-warnings --no-subspecs
+
+ # pod lib lint to check build and warnings for static library build - only on cron jobs
+ - stage: test
+ env:
+ - PROJECT=Firebase PLATFORM=iOS METHOD=pod-lib-lint
+ before_install:
+ - ./scripts/if_cron.sh ./scripts/install_prereqs.sh
+ script:
+ - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseCore.podspec --use-libraries
+ - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseAuth.podspec --use-libraries
+ - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseDatabase.podspec --use-libraries
+ # The Protobuf dependency of FirebaseMessaging has warnings with --use-libraries
+# - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseMessaging.podspec --use-libraries --allow-warnings
+ - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseStorage.podspec --use-libraries
+ - ./scripts/if_cron.sh bundle exec pod lib lint FirebaseFunctions.podspec --use-libraries
+
+ - stage: test
+ env:
+ - PROJECT=Firestore PLATFORM=iOS METHOD=pod-lib-lint
+ before_install:
+ - ./scripts/if_cron.sh ./scripts/install_prereqs.sh
+ script:
+ # TBD - non-portable path warnings
+ # The travis_wait is necessary because the command takes more than 10 minutes.
+ - travis_wait ./scripts/if_cron.sh bundle exec pod lib lint FirebaseFirestore.podspec --use-libraries --allow-warnings --no-subspecs
+
# Alternative platforms
- stage: test
@@ -65,7 +104,6 @@ jobs:
- PROJECT=Firestore PLATFORM=macOS METHOD=cmake
before_install:
- ./scripts/if_changed.sh ./scripts/install_prereqs.sh
- - ./scripts/if_changed.sh ./scripts/pod_install.sh
script:
- ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM $METHOD
@@ -73,19 +111,17 @@ jobs:
- stage: test
env:
- - PROJECT=Firebase PLATFORM=macOS
+ - PROJECT=Firebase PLATFORM=macOS METHOD=xcodebuild
before_install:
- ./scripts/if_changed.sh ./scripts/install_prereqs.sh
- - ./scripts/if_changed.sh ./scripts/pod_install.sh
script:
- ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM
- stage: test
env:
- - PROJECT=Firebase PLATFORM=tvOS
+ - PROJECT=Firebase PLATFORM=tvOS METHOD=xcodebuild
before_install:
- ./scripts/if_changed.sh ./scripts/install_prereqs.sh
- - ./scripts/if_changed.sh ./scripts/pod_install.sh
script:
- ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM
@@ -96,7 +132,6 @@ jobs:
- PROJECT=Firestore PLATFORM=iOS METHOD=xcodebuild SANITIZERS=asan
before_install:
- ./scripts/if_changed.sh ./scripts/install_prereqs.sh
- - ./scripts/if_changed.sh ./scripts/pod_install.sh
script:
- ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM $METHOD
@@ -105,7 +140,6 @@ jobs:
- PROJECT=Firestore PLATFORM=iOS METHOD=xcodebuild SANITIZERS=tsan
before_install:
- ./scripts/if_changed.sh ./scripts/install_prereqs.sh
- - ./scripts/if_changed.sh ./scripts/pod_install.sh
script:
- ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM $METHOD
@@ -118,7 +152,6 @@ jobs:
- PROJECT=Firestore PLATFORM=macOS METHOD=cmake SANITIZERS=asan
before_install:
- ./scripts/if_changed.sh ./scripts/install_prereqs.sh
- - ./scripts/if_changed.sh ./scripts/pod_install.sh
script:
- ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM $METHOD
@@ -127,7 +160,6 @@ jobs:
- PROJECT=Firestore PLATFORM=macOS METHOD=cmake SANITIZERS=tsan
before_install:
- ./scripts/if_changed.sh ./scripts/install_prereqs.sh
- - ./scripts/if_changed.sh ./scripts/pod_install.sh
script:
- ./scripts/if_changed.sh ./scripts/build.sh $PROJECT $PLATFORM $METHOD
diff --git a/Example/Auth/ApiTests/FirebaseAuthApiTests.m b/Example/Auth/ApiTests/FirebaseAuthApiTests.m
index 741814c..d4f4da2 100644
--- a/Example/Auth/ApiTests/FirebaseAuthApiTests.m
+++ b/Example/Auth/ApiTests/FirebaseAuthApiTests.m
@@ -130,7 +130,7 @@ static NSTimeInterval const kExpectationsTimeout = 10;
[self expectationWithDescription:@"Created account with email and password."];
[auth createUserWithEmail:kTestingEmailToCreateUser
password:@"password"
- completion:^(FIRUser *user, NSError *error) {
+ completion:^(FIRAuthDataResult *result, NSError *error) {
if (error) {
NSLog(@"createUserWithEmail has error: %@", error);
}
@@ -163,7 +163,7 @@ static NSTimeInterval const kExpectationsTimeout = 10;
[self expectationWithDescription:@"Created account with email and password."];
[auth createUserWithEmail:kTestingEmailToCreateUser
password:@"password"
- completion:^(FIRUser *user, NSError *error) {
+ completion:^(FIRAuthDataResult *user, NSError *error) {
apiError = error;
[expectation fulfill];
}];
@@ -240,7 +240,7 @@ static NSTimeInterval const kExpectationsTimeout = 10;
[self expectationWithDescription:@"Signed in existing account with email and password."];
[auth signInWithEmail:kExistingTestingEmailToSignIn
password:@"password"
- completion:^(FIRUser *user, NSError *error) {
+ completion:^(FIRAuthDataResult *user, NSError *error) {
if (error) {
NSLog(@"Signing in existing account has error: %@", error);
}
@@ -277,7 +277,7 @@ static NSTimeInterval const kExpectationsTimeout = 10;
[self expectationWithDescription:@"CustomAuthToken sign-in finished."];
[auth signInWithCustomToken:customToken
- completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ completion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) {
if (error) {
NSLog(@"Valid token sign in error: %@", error);
}
@@ -314,7 +314,7 @@ static NSTimeInterval const kExpectationsTimeout = 10;
__block NSError *apiError;
[auth signInWithCustomToken:customToken
- completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ completion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) {
if (error) {
apiError = error;
}
@@ -349,7 +349,7 @@ static NSTimeInterval const kExpectationsTimeout = 10;
[self expectationWithDescription:@"CustomAuthToken sign-in finished."];
__block NSError *rpcError;
[auth signInWithCustomToken:customToken
- completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ completion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) {
if (error) {
rpcError = error;
}
@@ -390,7 +390,7 @@ static NSTimeInterval const kExpectationsTimeout = 10;
[self expectationWithDescription:@"Invalid CustomAuthToken sign-in finished."];
[auth signInWithCustomToken:kInvalidCustomToken
- completion:^(FIRUser *_Nullable user, NSError *_Nullable error) {
+ completion:^(FIRAuthDataResult *_Nullable result, NSError *_Nullable error) {
XCTAssertEqualObjects(error.localizedDescription, kInvalidTokenErrorMessage);
[expectation fulfill];
@@ -583,7 +583,7 @@ static NSTimeInterval const kExpectationsTimeout = 10;
XCTestExpectation *expectation =
[self expectationWithDescription:@"Anonymousy sign-in finished."];
- [auth signInAnonymouslyWithCompletion:^(FIRUser *user, NSError *error) {
+ [auth signInAnonymouslyWithCompletion:^(FIRAuthDataResult *result, NSError *error) {
if (error) {
NSLog(@"Anonymousy sign in error: %@", error);
}
diff --git a/Example/Core/Tests/FIRAppTest.m b/Example/Core/Tests/FIRAppTest.m
index 6825e6a..abf1d38 100644
--- a/Example/Core/Tests/FIRAppTest.m
+++ b/Example/Core/Tests/FIRAppTest.m
@@ -42,6 +42,9 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
+ (BOOL)validateAppIDFormat:(NSString *)appID withVersion:(NSString *)version;
+ (BOOL)validateAppIDFingerprint:(NSString *)appID withVersion:(NSString *)version;
++ (nullable NSNumber *)readDataCollectionSwitchFromPlist;
++ (nullable NSNumber *)readDataCollectionSwitchFromUserDefaultsForApp:(FIRApp *)app;
+
@end
@interface FIRAppTest : FIRTestCase
@@ -552,6 +555,133 @@ NSString *const kFIRTestAppName2 = @"test-app-name-2";
[FIRApp validateAppIDFingerprint:@"1:1337:ios:deadbeef:ab" withVersion:kGoodVersionV1]);
}
+#pragma mark - Automatic Data Collection Tests
+
+- (void)testGlobalDataCollectionNoFlags {
+ // Test: No flags set.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(nil);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(nil);
+
+ XCTAssertTrue([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionPlistSetEnabled {
+ // Test: Plist set to enabled, no override.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(@YES);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(nil);
+
+ XCTAssertTrue([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionPlistSetDisabled {
+ // Test: Plist set to disabled, no override.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(@NO);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(nil);
+
+ XCTAssertFalse([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionUserSpecifiedEnabled {
+ // Test: User specified as enabled, no plist value.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(nil);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(@YES);
+
+ XCTAssertTrue([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionUserSpecifiedDisabled {
+ // Test: User specified as disabled, no plist value.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(nil);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(@NO);
+
+ XCTAssertFalse([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionUserOverriddenEnabled {
+ // Test: User specified as enabled, with plist set as disabled.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(@NO);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(@YES);
+
+ XCTAssertTrue([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionUserOverriddenDisabled {
+ // Test: User specified as disabled, with plist set as enabled.
+ [FIRApp configure];
+ OCMStub([self.appClassMock readDataCollectionSwitchFromPlist]).andReturn(@YES);
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(@NO);
+
+ XCTAssertFalse([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionWriteToDefaults {
+ id defaultsMock = OCMPartialMock([NSUserDefaults standardUserDefaults]);
+ [FIRApp configure];
+
+ FIRApp *app = [FIRApp defaultApp];
+ app.automaticDataCollectionEnabled = YES;
+ NSString *key =
+ [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, app.name];
+ OCMVerify([defaultsMock setObject:@YES forKey:key]);
+
+ [FIRApp defaultApp].automaticDataCollectionEnabled = NO;
+ OCMVerify([defaultsMock setObject:@NO forKey:key]);
+
+ [defaultsMock stopMocking];
+}
+
+- (void)testGlobalDataCollectionClearedAfterDelete {
+ // Configure and disable data collection for the default FIRApp.
+ [FIRApp configure];
+ FIRApp *app = [FIRApp defaultApp];
+ app.automaticDataCollectionEnabled = NO;
+ XCTAssertFalse(app.isAutomaticDataCollectionEnabled);
+
+ // Delete the app, and verify that the switch was reset.
+ XCTestExpectation *deleteFinished =
+ [self expectationWithDescription:@"The app should successfully delete."];
+ [app deleteApp:^(BOOL success) {
+ if (success) {
+ [deleteFinished fulfill];
+ }
+ }];
+
+ // Wait for the delete to complete.
+ [self waitForExpectations:@[ deleteFinished ] timeout:1];
+
+ // Set up the default app again, and check the data collection flag.
+ [FIRApp configure];
+ XCTAssertTrue([FIRApp defaultApp].isAutomaticDataCollectionEnabled);
+}
+
+- (void)testGlobalDataCollectionNoDiagnosticsSent {
+ [FIRApp configure];
+
+ // Stub out reading from user defaults since stubbing out the BOOL has issues. If the data
+ // collection switch is disabled, the `sendLogs` call should return immediately and not fire a
+ // notification.
+ OCMStub([self.appClassMock readDataCollectionSwitchFromUserDefaultsForApp:OCMOCK_ANY])
+ .andReturn(@NO);
+ OCMReject([self.notificationCenterMock postNotificationName:kFIRAppDiagnosticsNotification
+ object:OCMOCK_ANY
+ userInfo:OCMOCK_ANY]);
+ NSError *error = [NSError errorWithDomain:@"com.firebase" code:42 userInfo:nil];
+ [[FIRApp defaultApp] sendLogsWithServiceName:@"Service" version:@"Version" error:error];
+}
+
#pragma mark - Internal Methods
- (void)testAuthGetUID {
diff --git a/Example/Core/Tests/FIROptionsTest.m b/Example/Core/Tests/FIROptionsTest.m
index 20aec94..064745a 100644
--- a/Example/Core/Tests/FIROptionsTest.m
+++ b/Example/Core/Tests/FIROptionsTest.m
@@ -17,6 +17,7 @@
#import <FirebaseCore/FIRAppInternal.h>
#import <FirebaseCore/FIRBundleUtil.h>
#import <FirebaseCore/FIROptionsInternal.h>
+#import <FirebaseCore/FIRVersion.h>
extern NSString *const kFIRIsMeasurementEnabled;
extern NSString *const kFIRIsAnalyticsCollectionEnabled;
@@ -437,4 +438,13 @@ extern NSString *const kFIRLibraryVersionID;
XCTAssertEqual(numberOfMatches, 1, @"Incorrect library version format.");
}
+- (void)testVersionConsistency {
+ const char *versionString = [kFIRLibraryVersionID UTF8String];
+ int major = versionString[0] - '0';
+ int minor = (versionString[1] - '0') * 10 + versionString[2] - '0';
+ int patch = (versionString[3] - '0') * 10 + versionString[4] - '0';
+ NSString *str = [NSString stringWithFormat:@"%d.%d.%d", major, minor, patch];
+ XCTAssertEqualObjects(str, [NSString stringWithUTF8String:(const char *)FIRCoreVersionString]);
+}
+
@end
diff --git a/Example/Firebase.xcodeproj/project.pbxproj b/Example/Firebase.xcodeproj/project.pbxproj
index 26e0c33..f924ed4 100644
--- a/Example/Firebase.xcodeproj/project.pbxproj
+++ b/Example/Firebase.xcodeproj/project.pbxproj
@@ -4707,6 +4707,10 @@
INFOPLIST_FILE = "Messaging/Sample/iOS/Messaging-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = YES;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = com.google.FirebaseMessagingSample.dev;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -4732,6 +4736,10 @@
INFOPLIST_FILE = "Messaging/Sample/iOS/Messaging-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = com.google.FirebaseMessagingSample.dev;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -5929,6 +5937,10 @@
INFOPLIST_FILE = "$(SRCROOT)/Database/App/iOS/Database-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = YES;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.google.Database-Example-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
};
@@ -5948,6 +5960,10 @@
INFOPLIST_FILE = "$(SRCROOT)/Database/App/iOS/Database-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.google.Database-Example-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
};
@@ -6047,6 +6063,10 @@
INFOPLIST_FILE = "$(SRCROOT)/Auth/App/iOS/Auth-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = YES;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.google.Auth-Example-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -6069,6 +6089,10 @@
INFOPLIST_FILE = "$(SRCROOT)/Auth/App/iOS/Auth-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.google.Auth-Example-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -6461,6 +6485,10 @@
);
INFOPLIST_FILE = "$(SRCROOT)/Storage/App/iOS/Storage-Info.plist";
MODULE_NAME = ExampleApp;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.google.Storage-Example-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
@@ -6482,6 +6510,10 @@
);
INFOPLIST_FILE = "$(SRCROOT)/Storage/App/iOS/Storage-Info.plist";
MODULE_NAME = ExampleApp;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.google.Storage-Example-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
@@ -6543,6 +6575,10 @@
INFOPLIST_FILE = "$(SRCROOT)/Core/App/iOS/Core-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = YES;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Example-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
};
@@ -6560,6 +6596,10 @@
INFOPLIST_FILE = "$(SRCROOT)/Core/App/iOS/Core-Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "com.google.Core-Example-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
};
diff --git a/Example/Messaging/Tests/FIRMessagingTest.m b/Example/Messaging/Tests/FIRMessagingTest.m
index adc830d..61ff136 100644
--- a/Example/Messaging/Tests/FIRMessagingTest.m
+++ b/Example/Messaging/Tests/FIRMessagingTest.m
@@ -75,6 +75,46 @@ extern NSString *const kFIRMessagingFCMTokenFetchAPNSOption;
XCTAssertTrue(_messaging.isAutoInitEnabled);
}
+- (void)testAutoInitEnableFlagOverrideGlobalTrue {
+ OCMStub([self.mockMessaging isGlobalAutomaticDataCollectionEnabled]).andReturn(YES);
+ id bundleMock = OCMPartialMock([NSBundle mainBundle]);
+ OCMStub([bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled]).andReturn(nil);
+ XCTAssertTrue(self.messaging.isAutoInitEnabled);
+
+ self.messaging.autoInitEnabled = NO;
+ XCTAssertFalse(self.messaging.isAutoInitEnabled);
+ [bundleMock stopMocking];
+}
+
+- (void)testAutoInitEnableFlagOverrideGlobalFalse {
+ OCMStub([self.mockMessaging isGlobalAutomaticDataCollectionEnabled]).andReturn(YES);
+ id bundleMock = OCMPartialMock([NSBundle mainBundle]);
+ OCMStub([bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled]).andReturn(nil);
+ XCTAssertTrue(self.messaging.isAutoInitEnabled);
+
+ self.messaging.autoInitEnabled = NO;
+ XCTAssertFalse(self.messaging.isAutoInitEnabled);
+ [bundleMock stopMocking];
+}
+
+- (void)testAutoInitEnableGlobalDefaultTrue {
+ OCMStub([self.mockMessaging isGlobalAutomaticDataCollectionEnabled]).andReturn(YES);
+ id bundleMock = OCMPartialMock([NSBundle mainBundle]);
+ OCMStub([bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled]).andReturn(nil);
+
+ XCTAssertTrue(self.messaging.isAutoInitEnabled);
+ [bundleMock stopMocking];
+}
+
+- (void)testAutoInitEnableGlobalDefaultFalse {
+ OCMStub([self.mockMessaging isGlobalAutomaticDataCollectionEnabled]).andReturn(NO);
+ id bundleMock = OCMPartialMock([NSBundle mainBundle]);
+ OCMStub([bundleMock objectForInfoDictionaryKey:kFIRMessagingPlistAutoInitEnabled]).andReturn(nil);
+
+ XCTAssertFalse(self.messaging.isAutoInitEnabled);
+ [bundleMock stopMocking];
+}
+
#pragma mark - Direct Channel Establishment Testing
// Should connect with valid token and application in foreground
diff --git a/Firebase/Auth/CHANGELOG.md b/Firebase/Auth/CHANGELOG.md
index 858d2a7..5a3fcef 100644
--- a/Firebase/Auth/CHANGELOG.md
+++ b/Firebase/Auth/CHANGELOG.md
@@ -1,3 +1,10 @@
+# v5.0.0
+- Adds APIs for phone Auth testing to bypass the verification flow (#1192).
+- Changes the callback block signature for sign in and create user methods
+ to provide an AuthDataResult that includes the user and user info (#1123, #1186).
+- Removes GoogleToolboxForMac dependency (#1175).
+- Removes misc. deprecated APIs (#1188, #1200)
+
# v4.6.1
- Fixes crash which occurred when certain Firebase IDTokens were being parsed (#1076).
diff --git a/Firebase/Auth/Source/FIRAuthErrorUtils.m b/Firebase/Auth/Source/FIRAuthErrorUtils.m
index f4dbb94..62c569c 100644
--- a/Firebase/Auth/Source/FIRAuthErrorUtils.m
+++ b/Firebase/Auth/Source/FIRAuthErrorUtils.m
@@ -357,7 +357,7 @@ static NSString *const kFIRAuthErrorMessageNotificationNotForwarded = @"If app d
*/
static NSString *const kFIRAuthErrorMessageAppNotVerified = @"Firebase could not retrieve the "
"silent push notification and therefore could not verify your app. Ensure that you configured "
- "your app correctly to recieve push notifications.";
+ "your app correctly to receive push notifications.";
/** @var kFIRAuthErrorMessageCaptchaCheckFailed
@brief Message for @c FIRAuthErrorCodeCaptchaCheckFailed error code.
diff --git a/Firebase/Auth/Source/Public/FIRAuth.h b/Firebase/Auth/Source/Public/FIRAuth.h
index a3ed960..cfc59b1 100644
--- a/Firebase/Auth/Source/Public/FIRAuth.h
+++ b/Firebase/Auth/Source/Public/FIRAuth.h
@@ -410,20 +410,6 @@ NS_SWIFT_NAME(Auth)
@param password The user's password.
@param completion Optionally; a block which is invoked when the sign in flow finishes, or is
canceled. Invoked asynchronously on the main thread in the future.
-
- @remarks Possible error codes:
-
- + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that email and password
- accounts are not enabled. Enable them in the Auth section of the
- Firebase console.
- + `FIRAuthErrorCodeUserDisabled` - Indicates the user's account is disabled.
- + `FIRAuthErrorCodeWrongPassword` - Indicates the user attempted
- sign in with an incorrect password.
- + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
-
-
-
- @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods.
*/
- (void)signInAndRetrieveDataWithEmail:(NSString *)email
password:(NSString *)password
@@ -543,21 +529,6 @@ NS_SWIFT_NAME(Auth)
`signInAnonymously(Completion:)` for Swift instead.
@param completion Optionally; a block which is invoked when the sign in finishes, or is
canceled. Invoked asynchronously on the main thread in the future.
-
- @remarks If there is already an anonymous user signed in, that user will be returned instead.
- If there is any other existing user signed in, that user will be signed out.
-
- @remarks Possible error codes:
-
- + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that anonymous accounts are
- not enabled. Enable them in the Auth section of the Firebase console.
-
-
- @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods.
-
- @remarks This method will only exist until the next major Firebase release following 4.x.x.
- After the next major release the method `signInAnonymouslyWithCompletion` will support the
- `FIRAuthDataResultCallback`.
*/
- (void)signInAnonymouslyAndRetrieveDataWithCompletion:
(nullable FIRAuthDataResultCallback)completion
@@ -592,22 +563,6 @@ NS_SWIFT_NAME(Auth)
@param token A self-signed custom auth token.
@param completion Optionally; a block which is invoked when the sign in finishes, or is
canceled. Invoked asynchronously on the main thread in the future.
-
- @remarks Possible error codes:
-
- + `FIRAuthErrorCodeInvalidCustomToken` - Indicates a validation error with
- the custom token.
-
- + `FIRAuthErrorCodeCustomTokenMismatch` - Indicates the service account and the API key
- belong to different projects.
-
-
-
- @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods.
-
- @remarks This method will only exist until the next major Firebase release following 4.x.x.
- After the next major release the method `createUserWithEmail:password:completion:` will
- support the `FIRAuthDataResultCallback`.
*/
- (void)signInAndRetrieveDataWithCustomToken:(NSString *)token
completion:(nullable FIRAuthDataResultCallback)completion
@@ -650,24 +605,6 @@ NS_SWIFT_NAME(Auth)
@param password The user's desired password.
@param completion Optionally; a block which is invoked when the sign up flow finishes, or is
canceled. Invoked asynchronously on the main thread in the future.
-
- @remarks Possible error codes:
-
- + `FIRAuthErrorCodeInvalidEmail` - Indicates the email address is malformed.
- + `FIRAuthErrorCodeEmailAlreadyInUse` - Indicates the email used to attempt sign up
- already exists. Call fetchProvidersForEmail to check which sign-in mechanisms the user
- used, and prompt the user to sign in with one of those.
- + `FIRAuthErrorCodeOperationNotAllowed` - Indicates that email and password accounts
- are not enabled. Enable them in the Auth section of the Firebase console.
- + `FIRAuthErrorCodeWeakPassword` - Indicates an attempt to set a password that is
- considered too weak. The NSLocalizedFailureReasonErrorKey field in the NSError.userInfo
- dictionary object will contain more detailed explanation that can be shown to the user.
-
- @remarks See `FIRAuthErrors` for a list of error codes that are common to all API methods.
-
- @remarks This method will only exist until the next major Firebase release following 4.x.x.
- After the next major release the method `createUserWithEmail:password:completion:` will
- support the `FIRAuthDataResultCallback`.
*/
- (void)createUserAndRetrieveDataWithEmail:(NSString *)email
password:(NSString *)password
diff --git a/Firebase/Core/FIRApp.m b/Firebase/Core/FIRApp.m
index 3d05f12..717da4e 100644
--- a/Firebase/Core/FIRApp.m
+++ b/Firebase/Core/FIRApp.m
@@ -46,6 +46,11 @@ NSString *const kFIRAppIsDefaultAppKey = @"FIRAppIsDefaultAppKey";
NSString *const kFIRAppNameKey = @"FIRAppNameKey";
NSString *const kFIRGoogleAppIDKey = @"FIRGoogleAppIDKey";
+NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat =
+ @"/google/firebase/global_data_collection_enabled:%@";
+NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey =
+ @"FirebaseAutomaticDataCollectionEnabled";
+
NSString *const kFIRAppDiagnosticsNotification = @"FIRAppDiagnosticsNotification";
NSString *const kFIRAppDiagnosticsConfigurationTypeKey = @"ConfigType";
@@ -227,6 +232,7 @@ static NSMutableDictionary *sLibraryVersions;
if (sAllApps && sAllApps[self.name]) {
FIRLogDebug(kFIRLoggerCore, @"I-COR000006", @"Deleting app named %@", self.name);
[sAllApps removeObjectForKey:self.name];
+ [self clearDataCollectionSwitchFromUserDefaults];
if ([self.name isEqualToString:kFIRDefaultAppName]) {
sDefaultApp = nil;
}
@@ -332,6 +338,30 @@ static NSMutableDictionary *sLibraryVersions;
return [_options copy];
}
+- (void)setAutomaticDataCollectionEnabled:(BOOL)automaticDataCollectionEnabled {
+ NSString *key =
+ [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name];
+ [[NSUserDefaults standardUserDefaults] setBool:automaticDataCollectionEnabled forKey:key];
+}
+
+- (BOOL)isAutomaticDataCollectionEnabled {
+ // Check if it's been manually set before in code, and use that as the higher priority value.
+ NSNumber *defaultsObject = [[self class] readDataCollectionSwitchFromUserDefaultsForApp:self];
+ if (defaultsObject) {
+ return [defaultsObject boolValue];
+ }
+
+ // Read the Info.plist to see if the flag is set. If it's not set, it should default to `YES`.
+ // As per the implementation of `readDataCollectionSwitchFromPlist`, it's a cached value and has
+ // no performance impact calling multiple times.
+ NSNumber *collectionEnabledPlistValue = [[self class] readDataCollectionSwitchFromPlist];
+ if (collectionEnabledPlistValue) {
+ return [collectionEnabledPlistValue boolValue];
+ }
+
+ return YES;
+}
+
#pragma mark - private
+ (void)sendNotificationsToSDKs:(FIRApp *)app {
@@ -613,11 +643,64 @@ static NSMutableDictionary *sLibraryVersions;
}
// end App ID validation
-#pragma mark
+
+#pragma mark - Reading From Plist & User Defaults
+
+/**
+ * Clears the data collection switch from the standard NSUserDefaults for easier testing and
+ * readability.
+ */
+- (void)clearDataCollectionSwitchFromUserDefaults {
+ NSString *key =
+ [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, self.name];
+ [[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
+}
+
+/**
+ * Reads the data collection switch from the standard NSUserDefaults for easier testing and
+ * readability.
+ */
++ (nullable NSNumber *)readDataCollectionSwitchFromUserDefaultsForApp:(FIRApp *)app {
+ // Read the object in user defaults, and only return if it's an NSNumber.
+ NSString *key =
+ [NSString stringWithFormat:kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat, app.name];
+ id collectionEnabledDefaultsObject = [[NSUserDefaults standardUserDefaults] objectForKey:key];
+ if ([collectionEnabledDefaultsObject isKindOfClass:[NSNumber class]]) {
+ return collectionEnabledDefaultsObject;
+ }
+
+ return nil;
+}
+
+/**
+ * Reads the data collection switch from the Info.plist for easier testing and readability. Will
+ * only read once from the plist and return the cached value.
+ */
++ (nullable NSNumber *)readDataCollectionSwitchFromPlist {
+ static NSNumber *collectionEnabledPlistObject;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ // Read the data from the `Info.plist`, only assign it if it's there and an NSNumber.
+ id plistValue = [[NSBundle mainBundle]
+ objectForInfoDictionaryKey:kFIRGlobalAppDataCollectionEnabledPlistKey];
+ if (plistValue && [plistValue isKindOfClass:[NSNumber class]]) {
+ collectionEnabledPlistObject = (NSNumber *)plistValue;
+ }
+ });
+
+ return collectionEnabledPlistObject;
+}
+
+#pragma mark - Sending Logs
- (void)sendLogsWithServiceName:(NSString *)serviceName
version:(NSString *)version
error:(NSError *)error {
+ // If the user has manually turned off data collection, return and don't send logs.
+ if (![self isAutomaticDataCollectionEnabled]) {
+ return;
+ }
+
NSMutableDictionary *userInfo = [[NSMutableDictionary alloc] initWithDictionary:@{
kFIRAppDiagnosticsConfigurationTypeKey : @(FIRConfigTypeSDK),
kFIRAppDiagnosticsSDKNameKey : serviceName,
diff --git a/Firebase/Core/Private/FIRAppInternal.h b/Firebase/Core/Private/FIRAppInternal.h
index b7cf5e8..66979eb 100644
--- a/Firebase/Core/Private/FIRAppInternal.h
+++ b/Firebase/Core/Private/FIRAppInternal.h
@@ -60,6 +60,22 @@ extern NSString *const kFIRAppIsDefaultAppKey;
extern NSString *const kFIRAppNameKey;
extern NSString *const kFIRGoogleAppIDKey;
+/**
+ * The format string for the User Defaults key used for storing the data collection enabled flag.
+ * This includes formatting to append the Firebase App's name.
+ */
+extern NSString *const kFIRGlobalAppDataCollectionEnabledDefaultsKeyFormat;
+
+/**
+ * The plist key used for storing the data collection enabled flag.
+ */
+extern NSString *const kFIRGlobalAppDataCollectionEnabledPlistKey;
+
+/**
+ * A notification fired containing diagnostic information when SDK errors occur.
+ */
+extern NSString *const kFIRAppDiagnosticsNotification;
+
/** @var FIRAuthStateDidChangeInternalNotification
@brief The name of the @c NSNotificationCenter notification which is posted when the auth state
changes (e.g. a new token has been produced, a user logs in or out). The object parameter of
@@ -181,6 +197,18 @@ typedef NSString *_Nullable (^FIRAppGetUIDImplementation)(void);
*/
- (nullable NSString *)getUID;
+/**
+ * WARNING: THIS SETTING DOES NOT WORK YET. IT WILL BE MOVED TO THE PUBLIC HEADER ONCE ALL SDKS
+ * CONFORM TO THIS PREFERENCE. DO NOT RELY ON IT.
+ *
+ * Gets or sets whether automatic data collection is enabled for all products. Defaults to `YES`
+ * unless `FirebaseAutomaticDataCollectionEnabled` is set to `NO` in your app's Info.plist. This
+ * value is persisted across runs of the app so that it can be set once when users have consented to
+ * collection.
+ */
+@property(nonatomic, readwrite, getter=isAutomaticDataCollectionEnabled)
+ BOOL automaticDataCollectionEnabled;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h b/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h
index 4ff285b..c2d3871 100644
--- a/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h
+++ b/Firebase/Database/Api/Private/FIRDataSnapshot_Private.h
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#import "FIRDataSnapshot.h"
#import "FIndexedNode.h"
#import "FTypedefs_Private.h"
diff --git a/Firebase/Messaging/FIRMessaging+FIRApp.m b/Firebase/Messaging/FIRMessaging+FIRApp.m
index 58ae3af..d48a3b4 100644
--- a/Firebase/Messaging/FIRMessaging+FIRApp.m
+++ b/Firebase/Messaging/FIRMessaging+FIRApp.m
@@ -72,6 +72,7 @@
}
self.fcmSenderID = [options.GCMSenderID copy];
+ self.globalAutomaticDataCollectionEnabled = [app isAutomaticDataCollectionEnabled];
// Swizzle remote-notification-related methods (app delegate and UNUserNotificationCenter)
if ([FIRMessagingRemoteNotificationsProxy canSwizzleMethods]) {
diff --git a/Firebase/Messaging/FIRMessaging.m b/Firebase/Messaging/FIRMessaging.m
index 339bd7d..6222420 100644
--- a/Firebase/Messaging/FIRMessaging.m
+++ b/Firebase/Messaging/FIRMessaging.m
@@ -75,7 +75,7 @@ NSString *const kFIRMessagingUserDefaultsKeyAutoInitEnabled =
NSString *const kFIRMessagingAPNSTokenType = @"APNSTokenType"; // APNS Token type key stored in user info.
-static NSString *const kFIRMessagingPlistAutoInitEnabled =
+NSString *const kFIRMessagingPlistAutoInitEnabled =
@"FirebaseMessagingAutoInitEnabled"; // Auto Init Enabled key stored in Info.plist
@interface FIRMessagingMessageInfo ()
@@ -471,8 +471,9 @@ static NSString *const kFIRMessagingPlistAutoInitEnabled =
if (isAutoInitEnabledObject) {
return [isAutoInitEnabledObject boolValue];
}
- // If none of above exists, we default assume FCM auto init is enabled.
- return YES;
+
+ // If none of above exists, we default to the global switch that comes from FIRApp.
+ return self.isGlobalAutomaticDataCollectionEnabled;
}
- (void)setAutoInitEnabled:(BOOL)autoInitEnabled {
diff --git a/Firebase/Messaging/FIRMessaging_Private.h b/Firebase/Messaging/FIRMessaging_Private.h
index 46daee0..6bac99d 100644
--- a/Firebase/Messaging/FIRMessaging_Private.h
+++ b/Firebase/Messaging/FIRMessaging_Private.h
@@ -25,6 +25,7 @@ typedef NS_ENUM(int8_t, FIRMessagingNetworkStatus) {
kFIRMessagingReachabilityReachableViaWWAN,
};
+FOUNDATION_EXPORT NSString *const kFIRMessagingPlistAutoInitEnabled;
FOUNDATION_EXPORT NSString *const kFIRMessagingUserDefaultsKeyAutoInitEnabled;
@interface FIRMessagingRemoteMessage ()
@@ -37,6 +38,9 @@ FOUNDATION_EXPORT NSString *const kFIRMessagingUserDefaultsKeyAutoInitEnabled;
#pragma mark - Private API
+// The data collection flag from Core.
+@property(nonatomic, readwrite, getter=isGlobalAutomaticDataCollectionEnabled) BOOL globalAutomaticDataCollectionEnabled;
+
- (NSString *)defaultFcmToken;
- (FIRMessagingClient *)client;
- (FIRMessagingPubSub *)pubsub;
diff --git a/FirebaseMessaging.podspec b/FirebaseMessaging.podspec
index ad10553..8e6d7de 100644
--- a/FirebaseMessaging.podspec
+++ b/FirebaseMessaging.podspec
@@ -35,7 +35,6 @@ device, and it is completely free.
'GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1 ' +
'FIRMessaging_LIB_VERSION=' + String(s.version)
}
- s.framework = 'AddressBook'
s.framework = 'SystemConfiguration'
s.dependency 'FirebaseCore', '~> 5.0'
s.dependency 'FirebaseInstanceID', '~> 3.0'
diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md
index 5543325..70bab4e 100644
--- a/Firestore/CHANGELOG.md
+++ b/Firestore/CHANGELOG.md
@@ -1,4 +1,9 @@
# Unreleased
+- [fixed] Fixed an issue where `FirestoreSettings` would accept a concurrent
+ dispatch queue, but this configuration would trigger an assertion failure.
+ Passing a concurrent dispatch queue should now work correctly (#988).
+
+# v0.12.0
- [changed] Replaced the `DocumentListenOptions` object with a simple boolean.
Instead of calling
`addSnapshotListener(options: DocumentListenOptions.includeMetadataChanges(true))`
@@ -24,6 +29,8 @@
Query.getDocuments() should fetch from server only, cache only, or attempt
server and fall back to the cache (which was the only option previously, and
is now the default.)
+- [feature] Added new `mergeFields:(NSArray<id>*)` override for `set()`
+ which allows merging of a reduced subset of fields.
# v0.11.0
- [fixed] Fixed a regression in the Firebase iOS SDK release 4.11.0 that could
diff --git a/Firestore/Example/Firestore/Base.lproj/LaunchScreen.storyboard b/Firestore/Example/App/iOS/Base.lproj/LaunchScreen.storyboard
index 66a7681..66a7681 100644
--- a/Firestore/Example/Firestore/Base.lproj/LaunchScreen.storyboard
+++ b/Firestore/Example/App/iOS/Base.lproj/LaunchScreen.storyboard
diff --git a/Firestore/Example/Firestore/Base.lproj/Main.storyboard b/Firestore/Example/App/iOS/Base.lproj/Main.storyboard
index d164a23..d164a23 100644
--- a/Firestore/Example/Firestore/Base.lproj/Main.storyboard
+++ b/Firestore/Example/App/iOS/Base.lproj/Main.storyboard
diff --git a/Firestore/Example/Firestore/FIRAppDelegate.h b/Firestore/Example/App/iOS/FIRAppDelegate.h
index 1eb5040..1eb5040 100644
--- a/Firestore/Example/Firestore/FIRAppDelegate.h
+++ b/Firestore/Example/App/iOS/FIRAppDelegate.h
diff --git a/Firestore/Example/Firestore/FIRAppDelegate.m b/Firestore/Example/App/iOS/FIRAppDelegate.m
index 12ca249..12ca249 100644
--- a/Firestore/Example/Firestore/FIRAppDelegate.m
+++ b/Firestore/Example/App/iOS/FIRAppDelegate.m
diff --git a/Firestore/Example/Firestore/FIRViewController.h b/Firestore/Example/App/iOS/FIRViewController.h
index 64b4b74..64b4b74 100644
--- a/Firestore/Example/Firestore/FIRViewController.h
+++ b/Firestore/Example/App/iOS/FIRViewController.h
diff --git a/Firestore/Example/Firestore/FIRViewController.m b/Firestore/Example/App/iOS/FIRViewController.m
index cdad545..cdad545 100644
--- a/Firestore/Example/Firestore/FIRViewController.m
+++ b/Firestore/Example/App/iOS/FIRViewController.m
diff --git a/Firestore/Example/Firestore/Firestore-Info.plist b/Firestore/Example/App/iOS/Firestore-Info.plist
index 7576a0d..7576a0d 100644
--- a/Firestore/Example/Firestore/Firestore-Info.plist
+++ b/Firestore/Example/App/iOS/Firestore-Info.plist
diff --git a/Firestore/Example/Firestore/Images.xcassets/AppIcon.appiconset/Contents.json b/Firestore/Example/App/iOS/Images.xcassets/AppIcon.appiconset/Contents.json
index d7070bc..d7070bc 100644
--- a/Firestore/Example/Firestore/Images.xcassets/AppIcon.appiconset/Contents.json
+++ b/Firestore/Example/App/iOS/Images.xcassets/AppIcon.appiconset/Contents.json
diff --git a/Firestore/Example/Firestore/en.lproj/InfoPlist.strings b/Firestore/Example/App/iOS/en.lproj/InfoPlist.strings
index 477b28f..477b28f 100644
--- a/Firestore/Example/Firestore/en.lproj/InfoPlist.strings
+++ b/Firestore/Example/App/iOS/en.lproj/InfoPlist.strings
diff --git a/Firestore/Example/Firestore/main.m b/Firestore/Example/App/iOS/main.m
index 724fccf..724fccf 100644
--- a/Firestore/Example/Firestore/main.m
+++ b/Firestore/Example/App/iOS/main.m
diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj
index 8aecc9f..ed75d8c 100644
--- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj
+++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj
@@ -25,11 +25,12 @@
/* Begin PBXBuildFile section */
132E3E53179DE287D875F3F2 /* FSTLevelDBTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */; };
- 347FDC6AA737A754541F7C8A /* Pods_Firestore_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8525646842C83F703237BAA4 /* Pods_Firestore_Tests_iOS.framework */; };
3B843E4C1F3A182900548890 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; };
5436F32420008FAD006E51E3 /* string_printf_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5436F32320008FAD006E51E3 /* string_printf_test.cc */; };
+ 54511E8E209805F8005BD28F /* hashing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54511E8D209805F8005BD28F /* hashing_test.cc */; };
5467FB01203E5717009C9584 /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; };
5467FB08203E6A44009C9584 /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; };
+ 546854AA20A36867004BDBD5 /* datastore_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 546854A820A36867004BDBD5 /* datastore_test.cc */; };
54740A571FC914BA00713A1A /* secure_random_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A531FC913E500713A1A /* secure_random_test.cc */; };
54740A581FC914F000713A1A /* autoid_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A521FC913E500713A1A /* autoid_test.cc */; };
54764FAF1FAA21B90085E60A /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; };
@@ -108,6 +109,17 @@
5492E0CA2021557E00B64F25 /* FSTWatchChangeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C52021557E00B64F25 /* FSTWatchChangeTests.mm */; };
5495EB032040E90200EBA509 /* CodableGeoPointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5495EB022040E90200EBA509 /* CodableGeoPointTests.swift */; };
54995F6F205B6E12004EFFA0 /* leveldb_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */; };
+ 549CCA5020A36DBC00BCEB75 /* sorted_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4C20A36DBB00BCEB75 /* sorted_set_test.cc */; };
+ 549CCA5120A36DBC00BCEB75 /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; };
+ 549CCA5220A36DBC00BCEB75 /* sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */; };
+ 549CCA5720A36E1F00BCEB75 /* field_mask_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5320A36E1F00BCEB75 /* field_mask_test.cc */; };
+ 549CCA5920A36E1F00BCEB75 /* precondition_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5520A36E1F00BCEB75 /* precondition_test.cc */; };
+ 54A0352620A3AED0003E0143 /* field_transform_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352320A3AEC3003E0143 /* field_transform_test.mm */; };
+ 54A0352720A3AED0003E0143 /* transform_operations_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352220A3AEC3003E0143 /* transform_operations_test.mm */; };
+ 54A0352A20A3B3BD003E0143 /* testutil.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352820A3B3BD003E0143 /* testutil.cc */; };
+ 54A0352F20A3B3D8003E0143 /* status_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352C20A3B3D7003E0143 /* status_test.cc */; };
+ 54A0353020A3B3D8003E0143 /* statusor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352D20A3B3D7003E0143 /* statusor_test.cc */; };
+ 54A0353520A3D8CB003E0143 /* iterator_adaptors_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0353420A3D8CB003E0143 /* iterator_adaptors_test.cc */; };
54C2294F1FECABAE007D065B /* log_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54C2294E1FECABAE007D065B /* log_test.cc */; };
54DA12A61F315EE100DD57A1 /* collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129C1F315EE100DD57A1 /* collection_spec_test.json */; };
54DA12A71F315EE100DD57A1 /* existence_filter_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129D1F315EE100DD57A1 /* existence_filter_spec_test.json */; };
@@ -120,6 +132,7 @@
54DA12AE1F315EE100DD57A1 /* resume_token_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A41F315EE100DD57A1 /* resume_token_spec_test.json */; };
54DA12AF1F315EE100DD57A1 /* write_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A51F315EE100DD57A1 /* write_spec_test.json */; };
54EB764D202277B30088B8F3 /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; };
+ 5D405BE298CE4692CB00790A /* Pods_Firestore_Tests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2B50B3A0DF77100EEE887891 /* Pods_Firestore_Tests_iOS.framework */; };
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; };
6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
@@ -137,6 +150,7 @@
7346E61D20325C6900FD6CEF /* FSTDispatchQueueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7346E61C20325C6900FD6CEF /* FSTDispatchQueueTests.mm */; };
73866AA12082B0A5009BB4FF /* FIRArrayTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73866A9F2082B069009BB4FF /* FIRArrayTransformTests.mm */; };
873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; };
+ 8C82D4D3F9AB63E79CC52DC8 /* Pods_Firestore_IntegrationTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */; };
AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; };
AB380CFB2019388600D97691 /* target_id_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CF82019382300D97691 /* target_id_generator_test.cc */; };
AB380CFE201A2F4500D97691 /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CFC201A2EE200D97691 /* string_util_test.cc */; };
@@ -155,18 +169,22 @@
ABC1D7E42024AFDE00BA84F0 /* firebase_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */; };
ABE6637A201FA81900ED349A /* database_id_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB71064B201FA60300344F18 /* database_id_test.cc */; };
ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; };
- AFE6114F0D4DAECBA7B7C089 /* Pods_Firestore_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */; };
B6152AD7202A53CB000E5744 /* document_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6152AD5202A5385000E5744 /* document_key_test.cc */; };
B65D34A9203C995B0076A5E1 /* FIRTimestampTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */; };
B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; };
B686F2B22025000D0028D6BE /* resource_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2B02024FFD70028D6BE /* resource_path_test.cc */; };
- C4E749275AD0FBDF9F4716A8 /* Pods_SwiftBuildTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */; };
- CF08376B68945A0BB332D0C8 /* Pods_Firestore_IntegrationTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B4EEE10E8E59CC91309335CA /* Pods_Firestore_IntegrationTests_iOS.framework */; };
- D9AF2279747DE7213156646C /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0E5F50EF80014608B1868944 /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */; };
+ B6FB467D208E9D3C00554BA2 /* async_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB467B208E9A8200554BA2 /* async_queue_test.cc */; };
+ B6FB4684208EA0EC00554BA2 /* async_queue_libdispatch_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4680208EA0BE00554BA2 /* async_queue_libdispatch_test.mm */; };
+ B6FB4685208EA0F000554BA2 /* async_queue_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4681208EA0BE00554BA2 /* async_queue_std_test.cc */; };
+ B6FB468E208F9BAB00554BA2 /* executor_libdispatch_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4689208F9B9100554BA2 /* executor_libdispatch_test.mm */; };
+ B6FB468F208F9BAE00554BA2 /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; };
+ B6FB4690208F9BB300554BA2 /* executor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4688208F9B9100554BA2 /* executor_test.cc */; };
+ BF219E98F1C5A1DAEB5EEC86 /* Pods_Firestore_Example_iOS_SwiftBuildTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 379B34A1536045869826D82A /* Pods_Firestore_Example_iOS_SwiftBuildTest.framework */; };
+ C1AA536F90A0A576CA2816EB /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BB92EB03E3F92485023F64ED /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */; };
+ C8D3CE2343E53223E6487F2C /* Pods_Firestore_Example_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5918805E993304321A05E82B /* Pods_Firestore_Example_iOS.framework */; };
DE03B2D41F2149D600A30B9C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
DE03B2D51F2149D600A30B9C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
DE03B2D61F2149D600A30B9C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
- DE03B2D71F2149D600A30B9C /* Pods_Firestore_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 69F6A10DBD6187489481CD76 /* Pods_Firestore_Tests.framework */; };
DE03B2DD1F2149D600A30B9C /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
DE03B3631F215E1A00A30B9C /* CAcert.pem in Resources */ = {isa = PBXBuildFile; fileRef = DE03B3621F215E1600A30B9C /* CAcert.pem */; };
DE0761F81F2FE68D003233AF /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0761F61F2FE68D003233AF /* main.swift */; };
@@ -174,7 +192,6 @@
DE2EF0861F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0801F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m */; };
DE2EF0871F3D0B6E003D0CDC /* FSTImmutableSortedSet+Testing.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0821F3D0B6E003D0CDC /* FSTImmutableSortedSet+Testing.m */; };
DE2EF0881F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m in Sources */ = {isa = PBXBuildFile; fileRef = DE2EF0841F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m */; };
- F06EE8EB234BBE8B7898D5EE /* Pods_Firestore_Example_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24A6BEC38BF31BC4BF0E9DA7 /* Pods_Firestore_Example_iOS.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -230,27 +247,27 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
- 04DF37A117F88A9891379ED6 /* Pods-Firestore_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests/Pods-Firestore_Tests.release.xcconfig"; sourceTree = "<group>"; };
- 0E5F50EF80014608B1868944 /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 05C3D82261C3BE976889FF09 /* Pods-Firestore_Example_iOS-SwiftBuildTest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-SwiftBuildTest.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-SwiftBuildTest/Pods-Firestore_Example_iOS-SwiftBuildTest.debug.xcconfig"; sourceTree = "<group>"; };
+ 11984BA0A99D7A7ABA5B0D90 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.release.xcconfig"; sourceTree = "<group>"; };
+ 1277F98C20D2DF0867496976 /* Pods-Firestore_IntegrationTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
12F4357299652983A615F886 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
132E36BB104830BD806351AC /* FSTLevelDBTransactionTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBTransactionTests.mm; sourceTree = "<group>"; };
- 245812330F6A31632BB4B623 /* Pods_Firestore_Example_Firestore_SwiftTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_Firestore_SwiftTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 24A6BEC38BF31BC4BF0E9DA7 /* Pods_Firestore_Example_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftBuildTest.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 2B50B3A0DF77100EEE887891 /* Pods_Firestore_Tests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Tests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 379B34A1536045869826D82A /* Pods_Firestore_Example_iOS_SwiftBuildTest.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_iOS_SwiftBuildTest.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3B843E4A1F3930A400548890 /* remote_store_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = remote_store_spec_test.json; sourceTree = "<group>"; };
- 3C7CE22C50805C4A854C73A1 /* Pods-Firestore_IntegrationTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 3F422FFBDA6E79396E2FB594 /* Pods-Firestore_Example-Firestore_SwiftTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example-Firestore_SwiftTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example-Firestore_SwiftTests_iOS/Pods-Firestore_Example-Firestore_SwiftTests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 42491D7DC8C8CD245CC22B93 /* Pods-SwiftBuildTest.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftBuildTest.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftBuildTest/Pods-SwiftBuildTest.debug.xcconfig"; sourceTree = "<group>"; };
- 4EBC5F5ABE1FD097EFE5E224 /* Pods-Firestore_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example/Pods-Firestore_Example.release.xcconfig"; sourceTree = "<group>"; };
- 5436F32320008FAD006E51E3 /* string_printf_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = string_printf_test.cc; path = ../../core/test/firebase/firestore/util/string_printf_test.cc; sourceTree = "<group>"; };
+ 3C81DE3772628FE297055662 /* Pods-Firestore_Example_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS.debug.xcconfig"; sourceTree = "<group>"; };
+ 3F0992A4B83C60841C52E960 /* Pods-Firestore_Example_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS.release.xcconfig"; sourceTree = "<group>"; };
+ 5436F32320008FAD006E51E3 /* string_printf_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_printf_test.cc; sourceTree = "<group>"; };
+ 54511E8D209805F8005BD28F /* hashing_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = hashing_test.cc; sourceTree = "<group>"; };
5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRFirestoreTests.mm; sourceTree = "<group>"; };
- 5467FB06203E6A44009C9584 /* app_testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = app_testing.h; path = ../../core/test/firebase/firestore/testutil/app_testing.h; sourceTree = "<group>"; };
- 5467FB07203E6A44009C9584 /* app_testing.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = app_testing.mm; path = ../../core/test/firebase/firestore/testutil/app_testing.mm; sourceTree = "<group>"; };
- 54740A521FC913E500713A1A /* autoid_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = autoid_test.cc; path = ../../core/test/firebase/firestore/util/autoid_test.cc; sourceTree = "<group>"; };
- 54740A531FC913E500713A1A /* secure_random_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = secure_random_test.cc; path = ../../core/test/firebase/firestore/util/secure_random_test.cc; sourceTree = "<group>"; };
- 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = FSTGoogleTestTests.mm; path = GoogleTest/FSTGoogleTestTests.mm; sourceTree = "<group>"; };
- 548DB926200D590300E00ABC /* assert_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = assert_test.cc; path = ../../core/test/firebase/firestore/util/assert_test.cc; sourceTree = "<group>"; };
- 548DB928200D59F600E00ABC /* comparison_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = comparison_test.cc; path = ../../core/test/firebase/firestore/util/comparison_test.cc; sourceTree = "<group>"; };
+ 5467FB06203E6A44009C9584 /* app_testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = app_testing.h; sourceTree = "<group>"; };
+ 5467FB07203E6A44009C9584 /* app_testing.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = app_testing.mm; sourceTree = "<group>"; };
+ 546854A820A36867004BDBD5 /* datastore_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = datastore_test.cc; sourceTree = "<group>"; };
+ 54740A521FC913E500713A1A /* autoid_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = autoid_test.cc; sourceTree = "<group>"; };
+ 54740A531FC913E500713A1A /* secure_random_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = secure_random_test.cc; sourceTree = "<group>"; };
+ 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = FSTGoogleTestTests.mm; path = ../../../../Example/Tests/GoogleTest/FSTGoogleTestTests.mm; sourceTree = "<group>"; };
+ 548DB926200D590300E00ABC /* assert_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = assert_test.cc; sourceTree = "<group>"; };
+ 548DB928200D59F600E00ABC /* comparison_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = comparison_test.cc; sourceTree = "<group>"; };
5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTIntegrationTestCase.mm; sourceTree = "<group>"; };
5492E02C20213FFB00B64F25 /* FSTLevelDBSpecTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBSpecTests.mm; sourceTree = "<group>"; };
5492E02D20213FFC00B64F25 /* FSTMockDatastore.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTMockDatastore.mm; sourceTree = "<group>"; };
@@ -327,8 +344,22 @@
5492E0C42021557E00B64F25 /* FSTWatchChange+Testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FSTWatchChange+Testing.h"; sourceTree = "<group>"; };
5492E0C52021557E00B64F25 /* FSTWatchChangeTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTWatchChangeTests.mm; sourceTree = "<group>"; };
5495EB022040E90200EBA509 /* CodableGeoPointTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodableGeoPointTests.swift; sourceTree = "<group>"; };
- 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = leveldb_key_test.cc; path = ../../core/test/firebase/firestore/local/leveldb_key_test.cc; sourceTree = "<group>"; };
- 54C2294E1FECABAE007D065B /* log_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = log_test.cc; path = ../../core/test/firebase/firestore/util/log_test.cc; sourceTree = "<group>"; };
+ 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_key_test.cc; sourceTree = "<group>"; };
+ 549CCA4C20A36DBB00BCEB75 /* sorted_set_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sorted_set_test.cc; sourceTree = "<group>"; };
+ 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = tree_sorted_map_test.cc; sourceTree = "<group>"; };
+ 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sorted_map_test.cc; sourceTree = "<group>"; };
+ 549CCA4F20A36DBC00BCEB75 /* testing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = testing.h; sourceTree = "<group>"; };
+ 549CCA5320A36E1F00BCEB75 /* field_mask_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = field_mask_test.cc; sourceTree = "<group>"; };
+ 549CCA5520A36E1F00BCEB75 /* precondition_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = precondition_test.cc; sourceTree = "<group>"; };
+ 54A0352220A3AEC3003E0143 /* transform_operations_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = transform_operations_test.mm; sourceTree = "<group>"; };
+ 54A0352320A3AEC3003E0143 /* field_transform_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = field_transform_test.mm; sourceTree = "<group>"; };
+ 54A0352820A3B3BD003E0143 /* testutil.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = testutil.cc; sourceTree = "<group>"; };
+ 54A0352920A3B3BD003E0143 /* testutil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = testutil.h; sourceTree = "<group>"; };
+ 54A0352B20A3B3D7003E0143 /* status_test_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = status_test_util.h; sourceTree = "<group>"; };
+ 54A0352C20A3B3D7003E0143 /* status_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = status_test.cc; sourceTree = "<group>"; };
+ 54A0352D20A3B3D7003E0143 /* statusor_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = statusor_test.cc; sourceTree = "<group>"; };
+ 54A0353420A3D8CB003E0143 /* iterator_adaptors_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = iterator_adaptors_test.cc; sourceTree = "<group>"; };
+ 54C2294E1FECABAE007D065B /* log_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = log_test.cc; sourceTree = "<group>"; };
54C9EDF12040E16300A969CD /* Firestore_SwiftTests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_SwiftTests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
54C9EDF52040E16300A969CD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54DA129C1F315EE100DD57A1 /* collection_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = collection_spec_test.json; sourceTree = "<group>"; };
@@ -344,9 +375,8 @@
54E9281C1F33950B00C1953E /* FSTEventAccumulator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSTEventAccumulator.h; sourceTree = "<group>"; };
54E9281E1F33950B00C1953E /* FSTIntegrationTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FSTIntegrationTestCase.h; sourceTree = "<group>"; };
54E9282A1F339CAD00C1953E /* XCTestCase+Await.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "XCTestCase+Await.h"; sourceTree = "<group>"; };
- 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = array_sorted_map_test.cc; path = ../../immutable/array_sorted_map_test.cc; sourceTree = "<group>"; };
- 555ACD6970390DC825A8F141 /* Pods-Firestore_Example_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 5A3E3BE5F322D66EE3D6CB65 /* Pods-Firestore_Tests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS.release.xcconfig"; sourceTree = "<group>"; };
+ 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = array_sorted_map_test.cc; sourceTree = "<group>"; };
+ 5918805E993304321A05E82B /* Pods_Firestore_Example_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
6003F58A195388D20070C39A /* Firestore_Example_iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Firestore_Example_iOS.app; sourceTree = BUILT_PRODUCTS_DIR; };
6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
@@ -364,24 +394,18 @@
6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
6161B5012047140400A99DBB /* FIRFirestoreSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRFirestoreSourceTests.mm; sourceTree = "<group>"; };
- 618AC3C38A174084B9420162 /* Pods-Firestore_IntegrationTests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS.release.xcconfig"; sourceTree = "<group>"; };
- 69F6A10DBD6187489481CD76 /* Pods_Firestore_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 69E6C311558EC77729A16CF1 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
7346E61C20325C6900FD6CEF /* FSTDispatchQueueTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTDispatchQueueTests.mm; sourceTree = "<group>"; };
73866A9F2082B069009BB4FF /* FIRArrayTransformTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRArrayTransformTests.mm; sourceTree = "<group>"; };
- 75A6FE51C1A02DF38F62FAAD /* Pods_Firestore_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 80025E2E892B94823962D11D /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.release.xcconfig"; sourceTree = "<group>"; };
- 8525646842C83F703237BAA4 /* Pods_Firestore_Tests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Tests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 74ACEC3603BE58B57A7E8D4C /* Pods-Firestore_Example_iOS-SwiftBuildTest.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-SwiftBuildTest.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-SwiftBuildTest/Pods-Firestore_Example_iOS-SwiftBuildTest.release.xcconfig"; sourceTree = "<group>"; };
873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
8E002F4AD5D9B6197C940847 /* Firestore.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Firestore.podspec; path = ../Firestore.podspec; sourceTree = "<group>"; };
- 9837221D251B8D40E7D7B454 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- 9D52E67EE96AA7E5D6F69748 /* Pods-Firestore_IntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests/Pods-Firestore_IntegrationTests.debug.xcconfig"; sourceTree = "<group>"; };
- 9EF477AD4B2B643FD320867A /* Pods-Firestore_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example/Pods-Firestore_Example.debug.xcconfig"; sourceTree = "<group>"; };
AB356EF6200EA5EB0089B766 /* field_value_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = field_value_test.cc; sourceTree = "<group>"; };
AB380CF82019382300D97691 /* target_id_generator_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = target_id_generator_test.cc; sourceTree = "<group>"; };
- AB380CFC201A2EE200D97691 /* string_util_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = string_util_test.cc; path = ../../core/test/firebase/firestore/util/string_util_test.cc; sourceTree = "<group>"; };
- AB380D01201BC69F00D97691 /* bits_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bits_test.cc; path = ../../core/test/firebase/firestore/util/bits_test.cc; sourceTree = "<group>"; };
- AB380D03201BC6E400D97691 /* ordered_code_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ordered_code_test.cc; path = ../../core/test/firebase/firestore/util/ordered_code_test.cc; sourceTree = "<group>"; };
+ AB380CFC201A2EE200D97691 /* string_util_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_util_test.cc; sourceTree = "<group>"; };
+ AB380D01201BC69F00D97691 /* bits_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bits_test.cc; sourceTree = "<group>"; };
+ AB380D03201BC6E400D97691 /* ordered_code_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ordered_code_test.cc; sourceTree = "<group>"; };
AB38D92E20235D22000A432D /* database_info_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = database_info_test.cc; sourceTree = "<group>"; };
AB38D93220239654000A432D /* user_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = user_test.cc; sourceTree = "<group>"; };
AB38D9342023966E000A432D /* credentials_provider_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = credentials_provider_test.cc; sourceTree = "<group>"; };
@@ -390,23 +414,27 @@
AB6B908520322E6D00CC290A /* maybe_document_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = maybe_document_test.cc; sourceTree = "<group>"; };
AB6B908720322E8800CC290A /* no_document_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = no_document_test.cc; sourceTree = "<group>"; };
AB71064B201FA60300344F18 /* database_id_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = database_id_test.cc; sourceTree = "<group>"; };
- AB7BAB332012B519001E0872 /* geo_point_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = geo_point_test.cc; path = ../../core/test/firebase/firestore/geo_point_test.cc; sourceTree = "<group>"; };
+ AB7BAB332012B519001E0872 /* geo_point_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = geo_point_test.cc; sourceTree = "<group>"; };
ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = snapshot_version_test.cc; sourceTree = "<group>"; };
ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = token_test.cc; sourceTree = "<group>"; };
ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = firebase_credentials_provider_test.mm; sourceTree = "<group>"; };
- ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = timestamp_test.cc; path = ../../core/test/firebase/firestore/timestamp_test.cc; sourceTree = "<group>"; };
- B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- B4EEE10E8E59CC91309335CA /* Pods_Firestore_IntegrationTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = timestamp_test.cc; sourceTree = "<group>"; };
+ B3F5B3AAE791A5911B9EAA82 /* Pods-Firestore_Tests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS.release.xcconfig"; sourceTree = "<group>"; };
B6152AD5202A5385000E5744 /* document_key_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = document_key_test.cc; sourceTree = "<group>"; };
B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FIRTimestampTest.m; sourceTree = "<group>"; };
B686F2AD2023DDB20028D6BE /* field_path_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = field_path_test.cc; sourceTree = "<group>"; };
B686F2B02024FFD70028D6BE /* resource_path_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = resource_path_test.cc; sourceTree = "<group>"; };
- BE88081EE627C46349C918EF /* Pods-Firestore_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
- C1D89E5405935366C88CC3E5 /* Pods-Firestore_Example-Firestore_SwiftTests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example-Firestore_SwiftTests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example-Firestore_SwiftTests_iOS/Pods-Firestore_Example-Firestore_SwiftTests_iOS.release.xcconfig"; sourceTree = "<group>"; };
- CE00BABB5A3AAB44A4C209E2 /* Pods-Firestore_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests/Pods-Firestore_Tests.debug.xcconfig"; sourceTree = "<group>"; };
+ B6FB467A208E9A8200554BA2 /* async_queue_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = async_queue_test.h; sourceTree = "<group>"; };
+ B6FB467B208E9A8200554BA2 /* async_queue_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = async_queue_test.cc; sourceTree = "<group>"; };
+ B6FB4680208EA0BE00554BA2 /* async_queue_libdispatch_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = async_queue_libdispatch_test.mm; sourceTree = "<group>"; };
+ B6FB4681208EA0BE00554BA2 /* async_queue_std_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = async_queue_std_test.cc; sourceTree = "<group>"; };
+ B6FB4686208F9B9100554BA2 /* async_tests_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = async_tests_util.h; sourceTree = "<group>"; };
+ B6FB4687208F9B9100554BA2 /* executor_std_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = executor_std_test.cc; sourceTree = "<group>"; };
+ B6FB4688208F9B9100554BA2 /* executor_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = executor_test.cc; sourceTree = "<group>"; };
+ B6FB4689208F9B9100554BA2 /* executor_libdispatch_test.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = executor_libdispatch_test.mm; sourceTree = "<group>"; };
+ B6FB468A208F9B9100554BA2 /* executor_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = executor_test.h; sourceTree = "<group>"; };
+ BB92EB03E3F92485023F64ED /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D3CC3DC5338DCAF43A211155 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
- D8CA124A5F9A704EA0E12785 /* Pods-Firestore_Example_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS.release.xcconfig"; sourceTree = "<group>"; };
- DB17FEDFB80770611A935A60 /* Pods-Firestore_IntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests/Pods-Firestore_IntegrationTests.release.xcconfig"; sourceTree = "<group>"; };
DE03B2E91F2149D600A30B9C /* Firestore_IntegrationTests_iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Firestore_IntegrationTests_iOS.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
DE03B3621F215E1600A30B9C /* CAcert.pem */ = {isa = PBXFileReference; lastKnownFileType = text; path = CAcert.pem; sourceTree = "<group>"; };
DE0761E41F2FE611003233AF /* SwiftBuildTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftBuildTest.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -423,7 +451,9 @@
DE51B1981F0D48AC0013853F /* FSTSpecTests.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTSpecTests.h; sourceTree = "<group>"; };
DE51B19A1F0D48AC0013853F /* FSTSyncEngineTestDriver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTSyncEngineTestDriver.h; sourceTree = "<group>"; };
DE51B1A71F0D48AC0013853F /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
- F23325524BEAF8D24F78AC88 /* Pods-SwiftBuildTest.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftBuildTest.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftBuildTest/Pods-SwiftBuildTest.release.xcconfig"; sourceTree = "<group>"; };
+ E592181BFD7C53C305123739 /* Pods-Firestore_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
+ ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ F354C0FE92645B56A6C6FD44 /* Pods-Firestore_IntegrationTests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_IntegrationTests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -431,7 +461,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- D9AF2279747DE7213156646C /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework in Frameworks */,
+ C1AA536F90A0A576CA2816EB /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -442,7 +472,7 @@
6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */,
6003F592195388D20070C39A /* UIKit.framework in Frameworks */,
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */,
- F06EE8EB234BBE8B7898D5EE /* Pods_Firestore_Example_iOS.framework in Frameworks */,
+ C8D3CE2343E53223E6487F2C /* Pods_Firestore_Example_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -453,7 +483,7 @@
6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */,
6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */,
6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */,
- 347FDC6AA737A754541F7C8A /* Pods_Firestore_Tests_iOS.framework in Frameworks */,
+ 5D405BE298CE4692CB00790A /* Pods_Firestore_Tests_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -464,9 +494,7 @@
DE03B2D41F2149D600A30B9C /* XCTest.framework in Frameworks */,
DE03B2D51F2149D600A30B9C /* UIKit.framework in Frameworks */,
DE03B2D61F2149D600A30B9C /* Foundation.framework in Frameworks */,
- DE03B2D71F2149D600A30B9C /* Pods_Firestore_Tests.framework in Frameworks */,
- AFE6114F0D4DAECBA7B7C089 /* Pods_Firestore_IntegrationTests.framework in Frameworks */,
- CF08376B68945A0BB332D0C8 /* Pods_Firestore_IntegrationTests_iOS.framework in Frameworks */,
+ 8C82D4D3F9AB63E79CC52DC8 /* Pods_Firestore_IntegrationTests_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -474,36 +502,68 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- C4E749275AD0FBDF9F4716A8 /* Pods_SwiftBuildTest.framework in Frameworks */,
+ BF219E98F1C5A1DAEB5EEC86 /* Pods_Firestore_Example_iOS_SwiftBuildTest.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
+ 543B4F0520A91E4B001F506D /* App */ = {
+ isa = PBXGroup;
+ children = (
+ 6003F593195388D20070C39A /* iOS */,
+ );
+ path = App;
+ sourceTree = "<group>";
+ };
5467FB05203E652F009C9584 /* testutil */ = {
isa = PBXGroup;
children = (
5467FB06203E6A44009C9584 /* app_testing.h */,
5467FB07203E6A44009C9584 /* app_testing.mm */,
+ 54A0352820A3B3BD003E0143 /* testutil.cc */,
+ 54A0352920A3B3BD003E0143 /* testutil.h */,
);
- name = testutil;
+ path = testutil;
+ sourceTree = "<group>";
+ };
+ 546854A720A3681B004BDBD5 /* remote */ = {
+ isa = PBXGroup;
+ children = (
+ 546854A820A36867004BDBD5 /* datastore_test.cc */,
+ );
+ path = remote;
sourceTree = "<group>";
};
54740A561FC913EB00713A1A /* util */ = {
isa = PBXGroup;
children = (
548DB926200D590300E00ABC /* assert_test.cc */,
+ B6FB4680208EA0BE00554BA2 /* async_queue_libdispatch_test.mm */,
+ B6FB4681208EA0BE00554BA2 /* async_queue_std_test.cc */,
+ B6FB467B208E9A8200554BA2 /* async_queue_test.cc */,
+ B6FB467A208E9A8200554BA2 /* async_queue_test.h */,
+ B6FB4686208F9B9100554BA2 /* async_tests_util.h */,
54740A521FC913E500713A1A /* autoid_test.cc */,
AB380D01201BC69F00D97691 /* bits_test.cc */,
548DB928200D59F600E00ABC /* comparison_test.cc */,
+ B6FB4689208F9B9100554BA2 /* executor_libdispatch_test.mm */,
+ B6FB4687208F9B9100554BA2 /* executor_std_test.cc */,
+ B6FB4688208F9B9100554BA2 /* executor_test.cc */,
+ B6FB468A208F9B9100554BA2 /* executor_test.h */,
+ 54511E8D209805F8005BD28F /* hashing_test.cc */,
+ 54A0353420A3D8CB003E0143 /* iterator_adaptors_test.cc */,
54C2294E1FECABAE007D065B /* log_test.cc */,
AB380D03201BC6E400D97691 /* ordered_code_test.cc */,
54740A531FC913E500713A1A /* secure_random_test.cc */,
+ 54A0352B20A3B3D7003E0143 /* status_test_util.h */,
+ 54A0352C20A3B3D7003E0143 /* status_test.cc */,
+ 54A0352D20A3B3D7003E0143 /* statusor_test.cc */,
5436F32320008FAD006E51E3 /* string_printf_test.cc */,
AB380CFC201A2EE200D97691 /* string_util_test.cc */,
);
- name = util;
+ path = util;
sourceTree = "<group>";
};
54764FAC1FAA0C390085E60A /* GoogleTests */ = {
@@ -514,6 +574,7 @@
54EB764B202277970088B8F3 /* immutable */,
54995F70205B6E1A004EFFA0 /* local */,
AB356EF5200E9D1A0089B766 /* model */,
+ 546854A720A3681B004BDBD5 /* remote */,
5467FB05203E652F009C9584 /* testutil */,
54740A561FC913EB00713A1A /* util */,
54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */,
@@ -521,6 +582,7 @@
ABF6506B201131F8005F2C74 /* timestamp_test.cc */,
);
name = GoogleTests;
+ path = ../../core/test/firebase/firestore;
sourceTree = "<group>";
};
5495EB012040E90200EBA509 /* Codable */ = {
@@ -536,7 +598,7 @@
children = (
54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */,
);
- name = local;
+ path = local;
sourceTree = "<group>";
};
54C9EDF22040E16300A969CD /* SwiftTests */ = {
@@ -553,22 +615,25 @@
isa = PBXGroup;
children = (
54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */,
+ 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */,
+ 549CCA4C20A36DBB00BCEB75 /* sorted_set_test.cc */,
+ 549CCA4F20A36DBC00BCEB75 /* testing.h */,
+ 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */,
);
- name = immutable;
- path = ../../core/test/firebase/firestore/core/immutable;
+ path = immutable;
sourceTree = "<group>";
};
6003F581195388D10070C39A = {
isa = PBXGroup;
children = (
+ 543B4F0520A91E4B001F506D /* App */,
60FF7A9C1954A5C5007DD14C /* Podspec Metadata */,
- 6003F593195388D20070C39A /* Example for Firestore */,
6003F5B5195388D20070C39A /* Tests */,
54C9EDF22040E16300A969CD /* SwiftTests */,
DE0761E51F2FE611003233AF /* SwiftBuildTest */,
6003F58C195388D20070C39A /* Frameworks */,
6003F58B195388D20070C39A /* Products */,
- A47A1BF74A48BCAEAFBCBF1E /* Pods */,
+ AAEA2A72CFD1FA5AD34462F7 /* Pods */,
);
sourceTree = "<group>";
};
@@ -591,20 +656,16 @@
6003F58F195388D20070C39A /* CoreGraphics.framework */,
6003F591195388D20070C39A /* UIKit.framework */,
6003F5AF195388D20070C39A /* XCTest.framework */,
- 75A6FE51C1A02DF38F62FAAD /* Pods_Firestore_Example.framework */,
- 69F6A10DBD6187489481CD76 /* Pods_Firestore_Tests.framework */,
- B2FA635DF5D116A67A7441CD /* Pods_Firestore_IntegrationTests.framework */,
- 32AD40BF6B0E849B07FFD05E /* Pods_SwiftBuildTest.framework */,
- 245812330F6A31632BB4B623 /* Pods_Firestore_Example_Firestore_SwiftTests_iOS.framework */,
- 24A6BEC38BF31BC4BF0E9DA7 /* Pods_Firestore_Example_iOS.framework */,
- 0E5F50EF80014608B1868944 /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */,
- 8525646842C83F703237BAA4 /* Pods_Firestore_Tests_iOS.framework */,
- B4EEE10E8E59CC91309335CA /* Pods_Firestore_IntegrationTests_iOS.framework */,
+ 5918805E993304321A05E82B /* Pods_Firestore_Example_iOS.framework */,
+ BB92EB03E3F92485023F64ED /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */,
+ 379B34A1536045869826D82A /* Pods_Firestore_Example_iOS_SwiftBuildTest.framework */,
+ ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */,
+ 2B50B3A0DF77100EEE887891 /* Pods_Firestore_Tests_iOS.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
- 6003F593195388D20070C39A /* Example for Firestore */ = {
+ 6003F593195388D20070C39A /* iOS */ = {
isa = PBXGroup;
children = (
6003F59C195388D20070C39A /* FIRAppDelegate.h */,
@@ -616,8 +677,7 @@
6003F5A8195388D20070C39A /* Images.xcassets */,
6003F594195388D20070C39A /* Supporting Files */,
);
- name = "Example for Firestore";
- path = Firestore;
+ path = iOS;
sourceTree = "<group>";
};
6003F594195388D20070C39A /* Supporting Files */ = {
@@ -667,27 +727,19 @@
name = "Podspec Metadata";
sourceTree = "<group>";
};
- A47A1BF74A48BCAEAFBCBF1E /* Pods */ = {
+ AAEA2A72CFD1FA5AD34462F7 /* Pods */ = {
isa = PBXGroup;
children = (
- 9EF477AD4B2B643FD320867A /* Pods-Firestore_Example.debug.xcconfig */,
- 4EBC5F5ABE1FD097EFE5E224 /* Pods-Firestore_Example.release.xcconfig */,
- 9D52E67EE96AA7E5D6F69748 /* Pods-Firestore_IntegrationTests.debug.xcconfig */,
- DB17FEDFB80770611A935A60 /* Pods-Firestore_IntegrationTests.release.xcconfig */,
- CE00BABB5A3AAB44A4C209E2 /* Pods-Firestore_Tests.debug.xcconfig */,
- 04DF37A117F88A9891379ED6 /* Pods-Firestore_Tests.release.xcconfig */,
- 42491D7DC8C8CD245CC22B93 /* Pods-SwiftBuildTest.debug.xcconfig */,
- F23325524BEAF8D24F78AC88 /* Pods-SwiftBuildTest.release.xcconfig */,
- 3F422FFBDA6E79396E2FB594 /* Pods-Firestore_Example-Firestore_SwiftTests_iOS.debug.xcconfig */,
- C1D89E5405935366C88CC3E5 /* Pods-Firestore_Example-Firestore_SwiftTests_iOS.release.xcconfig */,
- 555ACD6970390DC825A8F141 /* Pods-Firestore_Example_iOS.debug.xcconfig */,
- D8CA124A5F9A704EA0E12785 /* Pods-Firestore_Example_iOS.release.xcconfig */,
- 9837221D251B8D40E7D7B454 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */,
- 80025E2E892B94823962D11D /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.release.xcconfig */,
- BE88081EE627C46349C918EF /* Pods-Firestore_Tests_iOS.debug.xcconfig */,
- 5A3E3BE5F322D66EE3D6CB65 /* Pods-Firestore_Tests_iOS.release.xcconfig */,
- 3C7CE22C50805C4A854C73A1 /* Pods-Firestore_IntegrationTests_iOS.debug.xcconfig */,
- 618AC3C38A174084B9420162 /* Pods-Firestore_IntegrationTests_iOS.release.xcconfig */,
+ 3C81DE3772628FE297055662 /* Pods-Firestore_Example_iOS.debug.xcconfig */,
+ 3F0992A4B83C60841C52E960 /* Pods-Firestore_Example_iOS.release.xcconfig */,
+ 69E6C311558EC77729A16CF1 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */,
+ 11984BA0A99D7A7ABA5B0D90 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.release.xcconfig */,
+ 05C3D82261C3BE976889FF09 /* Pods-Firestore_Example_iOS-SwiftBuildTest.debug.xcconfig */,
+ 74ACEC3603BE58B57A7E8D4C /* Pods-Firestore_Example_iOS-SwiftBuildTest.release.xcconfig */,
+ 1277F98C20D2DF0867496976 /* Pods-Firestore_IntegrationTests_iOS.debug.xcconfig */,
+ F354C0FE92645B56A6C6FD44 /* Pods-Firestore_IntegrationTests_iOS.release.xcconfig */,
+ E592181BFD7C53C305123739 /* Pods-Firestore_Tests_iOS.debug.xcconfig */,
+ B3F5B3AAE791A5911B9EAA82 /* Pods-Firestore_Tests_iOS.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
@@ -695,28 +747,28 @@
AB356EF5200E9D1A0089B766 /* model */ = {
isa = PBXGroup;
children = (
+ AB71064B201FA60300344F18 /* database_id_test.cc */,
B6152AD5202A5385000E5744 /* document_key_test.cc */,
AB6B908320322E4D00CC290A /* document_test.cc */,
- B686F2B02024FFD70028D6BE /* resource_path_test.cc */,
+ 549CCA5320A36E1F00BCEB75 /* field_mask_test.cc */,
B686F2AD2023DDB20028D6BE /* field_path_test.cc */,
- AB71064B201FA60300344F18 /* database_id_test.cc */,
AB356EF6200EA5EB0089B766 /* field_value_test.cc */,
AB6B908520322E6D00CC290A /* maybe_document_test.cc */,
AB6B908720322E8800CC290A /* no_document_test.cc */,
+ 549CCA5520A36E1F00BCEB75 /* precondition_test.cc */,
+ B686F2B02024FFD70028D6BE /* resource_path_test.cc */,
ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */,
);
- name = model;
- path = ../../core/test/firebase/firestore/model;
+ path = model;
sourceTree = "<group>";
};
AB380CF7201937B800D97691 /* core */ = {
isa = PBXGroup;
children = (
- AB380CF82019382300D97691 /* target_id_generator_test.cc */,
AB38D92E20235D22000A432D /* database_info_test.cc */,
+ AB380CF82019382300D97691 /* target_id_generator_test.cc */,
);
- name = core;
- path = ../../core/test/firebase/firestore/core;
+ path = core;
sourceTree = "<group>";
};
AB38D9312023962A000A432D /* auth */ = {
@@ -728,8 +780,7 @@
ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */,
AB38D93220239654000A432D /* user_test.cc */,
);
- name = auth;
- path = ../../core/test/firebase/firestore/auth;
+ path = auth;
sourceTree = "<group>";
};
DE0761E51F2FE611003233AF /* SwiftBuildTest */ = {
@@ -789,11 +840,13 @@
DE51B17B1F0D48AC0013853F /* Model */ = {
isa = PBXGroup;
children = (
+ 54A0352320A3AEC3003E0143 /* field_transform_test.mm */,
5492E0B22021555000B64F25 /* FSTDocumentKeyTests.mm */,
5492E0B32021555100B64F25 /* FSTDocumentSetTests.mm */,
5492E0B62021555100B64F25 /* FSTDocumentTests.mm */,
5492E0B82021555100B64F25 /* FSTFieldValueTests.mm */,
5492E0B72021555100B64F25 /* FSTMutationTests.mm */,
+ 54A0352220A3AEC3003E0143 /* transform_operations_test.mm */,
);
path = Model;
sourceTree = "<group>";
@@ -934,11 +987,11 @@
isa = PBXNativeTarget;
buildConfigurationList = 54C9EDFA2040E16300A969CD /* Build configuration list for PBXNativeTarget "Firestore_SwiftTests_iOS" */;
buildPhases = (
- 6FB7F3A6D6ADAC64E4972A29 /* [CP] Check Pods Manifest.lock */,
+ D2D94DFA64939EF6DECDF908 /* [CP] Check Pods Manifest.lock */,
54C9EDED2040E16300A969CD /* Sources */,
54C9EDEE2040E16300A969CD /* Frameworks */,
54C9EDEF2040E16300A969CD /* Resources */,
- 9E2D564AC55ADE2D52B7E951 /* [CP] Embed Pods Frameworks */,
+ EA424838F4A5DD7B337F57AB /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -954,11 +1007,11 @@
isa = PBXNativeTarget;
buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "Firestore_Example_iOS" */;
buildPhases = (
- FAB3416C6DD87D45081EC3E8 /* [CP] Check Pods Manifest.lock */,
+ 83F2AB95D08093BB076EE521 /* [CP] Check Pods Manifest.lock */,
6003F586195388D20070C39A /* Sources */,
6003F587195388D20070C39A /* Frameworks */,
6003F588195388D20070C39A /* Resources */,
- 7C5123A9C345ECE100DA21BD /* [CP] Embed Pods Frameworks */,
+ 1EE692C7509A98D7EB03CA51 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -973,11 +1026,11 @@
isa = PBXNativeTarget;
buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "Firestore_Tests_iOS" */;
buildPhases = (
- 8D94B6319191CD7344A4D1B9 /* [CP] Check Pods Manifest.lock */,
+ 8B469EB6DA9E6404589402E2 /* [CP] Check Pods Manifest.lock */,
6003F5AA195388D20070C39A /* Sources */,
6003F5AB195388D20070C39A /* Frameworks */,
6003F5AC195388D20070C39A /* Resources */,
- BB3FE78ABF533BFC38839A0E /* [CP] Embed Pods Frameworks */,
+ 329C25E418360CEF62F6CB2B /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -993,11 +1046,11 @@
isa = PBXNativeTarget;
buildConfigurationList = DE03B2E61F2149D600A30B9C /* Build configuration list for PBXNativeTarget "Firestore_IntegrationTests_iOS" */;
buildPhases = (
- DE03B2971F2149D600A30B9C /* [CP] Check Pods Manifest.lock */,
+ A827A009A65B69DC1B80EAD4 /* [CP] Check Pods Manifest.lock */,
DE03B2981F2149D600A30B9C /* Sources */,
DE03B2D31F2149D600A30B9C /* Frameworks */,
DE03B2D81F2149D600A30B9C /* Resources */,
- DE03B2E41F2149D600A30B9C /* [CP] Embed Pods Frameworks */,
+ B7923D95031DB0DA112AAE9B /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -1013,11 +1066,11 @@
isa = PBXNativeTarget;
buildConfigurationList = DE0761F51F2FE611003233AF /* Build configuration list for PBXNativeTarget "SwiftBuildTest" */;
buildPhases = (
- 8F34C5E63ACEBD784CF82A45 /* [CP] Check Pods Manifest.lock */,
+ 5504F81EEBBEF943CA61D32C /* [CP] Check Pods Manifest.lock */,
DE0761E01F2FE611003233AF /* Sources */,
DE0761E11F2FE611003233AF /* Frameworks */,
DE0761E21F2FE611003233AF /* Resources */,
- 125BDFEB177CFD41D7A40928 /* [CP] Embed Pods Frameworks */,
+ 04404E0DCBB886A40E3C7175 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -1142,13 +1195,13 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
- 125BDFEB177CFD41D7A40928 /* [CP] Embed Pods Frameworks */ = {
+ 04404E0DCBB886A40E3C7175 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-SwiftBuildTest/Pods-SwiftBuildTest-frameworks.sh",
+ "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS-SwiftBuildTest/Pods-Firestore_Example_iOS-SwiftBuildTest-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/BoringSSL/openssl.framework",
"${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
"${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework",
@@ -1175,28 +1228,10 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftBuildTest/Pods-SwiftBuildTest-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 6FB7F3A6D6ADAC64E4972A29 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS-SwiftBuildTest/Pods-Firestore_Example_iOS-SwiftBuildTest-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
- 7C5123A9C345ECE100DA21BD /* [CP] Embed Pods Frameworks */ = {
+ 1EE692C7509A98D7EB03CA51 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1232,25 +1267,29 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
- 8D94B6319191CD7344A4D1B9 /* [CP] Check Pods Manifest.lock */ = {
+ 329C25E418360CEF62F6CB2B /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
+ "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS-frameworks.sh",
+ "${BUILT_PRODUCTS_DIR}/leveldb-library/leveldb.framework",
+ "${BUILT_PRODUCTS_DIR}/GoogleTest/GoogleTest.framework",
+ "${BUILT_PRODUCTS_DIR}/OCMock/OCMock.framework",
);
- name = "[CP] Check Pods Manifest.lock";
+ name = "[CP] Embed Pods Frameworks";
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Firestore_Tests_iOS-checkManifestLockResult.txt",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleTest.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
- 8F34C5E63ACEBD784CF82A45 /* [CP] Check Pods Manifest.lock */ = {
+ 5504F81EEBBEF943CA61D32C /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1261,72 +1300,50 @@
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-SwiftBuildTest-checkManifestLockResult.txt",
+ "$(DERIVED_FILE_DIR)/Pods-Firestore_Example_iOS-SwiftBuildTest-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- 9E2D564AC55ADE2D52B7E951 /* [CP] Embed Pods Frameworks */ = {
+ 83F2AB95D08093BB076EE521 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/BoringSSL/openssl.framework",
- "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework",
- "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework",
- "${BUILT_PRODUCTS_DIR}/gRPC/GRPCClient.framework",
- "${BUILT_PRODUCTS_DIR}/gRPC-Core/grpc.framework",
- "${BUILT_PRODUCTS_DIR}/gRPC-ProtoRPC/ProtoRPC.framework",
- "${BUILT_PRODUCTS_DIR}/gRPC-RxLibrary/RxLibrary.framework",
- "${BUILT_PRODUCTS_DIR}/leveldb-library/leveldb.framework",
- "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
);
- name = "[CP] Embed Pods Frameworks";
+ name = "[CP] Check Pods Manifest.lock";
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",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/grpc.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ProtoRPC.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxLibrary.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
+ "$(DERIVED_FILE_DIR)/Pods-Firestore_Example_iOS-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS-frameworks.sh\"\n";
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- BB3FE78ABF533BFC38839A0E /* [CP] Embed Pods Frameworks */ = {
+ 8B469EB6DA9E6404589402E2 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/leveldb-library/leveldb.framework",
- "${BUILT_PRODUCTS_DIR}/GoogleTest/GoogleTest.framework",
- "${BUILT_PRODUCTS_DIR}/OCMock/OCMock.framework",
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
);
- name = "[CP] Embed Pods Frameworks";
+ name = "[CP] Check Pods Manifest.lock";
outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleTest.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
+ "$(DERIVED_FILE_DIR)/Pods-Firestore_Tests_iOS-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS-frameworks.sh\"\n";
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- DE03B2971F2149D600A30B9C /* [CP] Check Pods Manifest.lock */ = {
+ A827A009A65B69DC1B80EAD4 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1344,7 +1361,7 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
- DE03B2E41F2149D600A30B9C /* [CP] Embed Pods Frameworks */ = {
+ B7923D95031DB0DA112AAE9B /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1362,7 +1379,7 @@
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
- FAB3416C6DD87D45081EC3E8 /* [CP] Check Pods Manifest.lock */ = {
+ D2D94DFA64939EF6DECDF908 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1373,13 +1390,49 @@
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Firestore_Example_iOS-checkManifestLockResult.txt",
+ "$(DERIVED_FILE_DIR)/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
+ EA424838F4A5DD7B337F57AB /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS-frameworks.sh",
+ "${BUILT_PRODUCTS_DIR}/BoringSSL/openssl.framework",
+ "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
+ "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework",
+ "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework",
+ "${BUILT_PRODUCTS_DIR}/gRPC/GRPCClient.framework",
+ "${BUILT_PRODUCTS_DIR}/gRPC-Core/grpc.framework",
+ "${BUILT_PRODUCTS_DIR}/gRPC-ProtoRPC/ProtoRPC.framework",
+ "${BUILT_PRODUCTS_DIR}/gRPC-RxLibrary/RxLibrary.framework",
+ "${BUILT_PRODUCTS_DIR}/leveldb-library/leveldb.framework",
+ "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework",
+ );
+ 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",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/grpc.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ProtoRPC.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxLibrary.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/leveldb.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
@@ -1409,6 +1462,7 @@
DE2EF0881F3D0B6E003D0CDC /* FSTTreeSortedDictionaryTests.m in Sources */,
ABE6637A201FA81900ED349A /* database_id_test.cc in Sources */,
5492E0AF2021552D00B64F25 /* FSTReferenceSetTests.mm in Sources */,
+ 549CCA5120A36DBC00BCEB75 /* tree_sorted_map_test.cc in Sources */,
5492E09E2021552D00B64F25 /* FSTEagerGarbageCollectorTests.mm in Sources */,
5492E0C62021557E00B64F25 /* FSTWatchChange+Testing.mm in Sources */,
5492E064202154B900B64F25 /* FSTQueryListenerTests.mm in Sources */,
@@ -1423,30 +1477,39 @@
5492E058202154AB00B64F25 /* FSTAPIHelpers.mm in Sources */,
AB380CFB2019388600D97691 /* target_id_generator_test.cc in Sources */,
5492E0A82021552D00B64F25 /* FSTLevelDBLocalStoreTests.mm in Sources */,
+ 549CCA5920A36E1F00BCEB75 /* precondition_test.cc in Sources */,
ABC1D7DE2023A05300BA84F0 /* user_test.cc in Sources */,
5491BC721FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */,
ABC1D7DD2023A04F00BA84F0 /* empty_credentials_provider_test.cc in Sources */,
DE2EF0861F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m in Sources */,
B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */,
+ 54A0352A20A3B3BD003E0143 /* testutil.cc in Sources */,
5492E03120213FFC00B64F25 /* FSTLevelDBSpecTests.mm in Sources */,
+ B6FB468F208F9BAE00554BA2 /* executor_std_test.cc in Sources */,
5492E0B12021552D00B64F25 /* FSTRemoteDocumentCacheTests.mm in Sources */,
5492E0BA2021555100B64F25 /* FSTDocumentSetTests.mm in Sources */,
54740A581FC914F000713A1A /* autoid_test.cc in Sources */,
548DB927200D590300E00ABC /* assert_test.cc in Sources */,
5492E0A62021552D00B64F25 /* FSTPersistenceTestHelpers.mm in Sources */,
+ B6FB468E208F9BAB00554BA2 /* executor_libdispatch_test.mm in Sources */,
5467FB01203E5717009C9584 /* FIRFirestoreTests.mm in Sources */,
+ B6FB4684208EA0EC00554BA2 /* async_queue_libdispatch_test.mm in Sources */,
5492E0A12021552D00B64F25 /* FSTMemoryLocalStoreTests.mm in Sources */,
5436F32420008FAD006E51E3 /* string_printf_test.cc in Sources */,
5492E067202154B900B64F25 /* FSTEventManagerTests.mm in Sources */,
+ 54A0353020A3B3D8003E0143 /* statusor_test.cc in Sources */,
5492E0BF2021555100B64F25 /* FSTFieldValueTests.mm in Sources */,
+ 54A0352620A3AED0003E0143 /* field_transform_test.mm in Sources */,
5492E055202154AB00B64F25 /* FIRDocumentSnapshotTests.mm in Sources */,
5492E03E2021401F00B64F25 /* FSTEventAccumulator.mm in Sources */,
+ 54A0352720A3AED0003E0143 /* transform_operations_test.mm in Sources */,
DE2EF0851F3D0B6E003D0CDC /* FSTArraySortedDictionaryTests.m in Sources */,
5492E0AA2021552D00B64F25 /* FSTLevelDBRemoteDocumentCacheTests.mm in Sources */,
5492E0AC2021552D00B64F25 /* FSTMutationQueueTests.mm in Sources */,
5492E056202154AB00B64F25 /* FIRFieldPathTests.mm in Sources */,
5492E03220213FFC00B64F25 /* FSTMockDatastore.mm in Sources */,
ABC1D7E12023A40C00BA84F0 /* token_test.cc in Sources */,
+ 54A0353520A3D8CB003E0143 /* iterator_adaptors_test.cc in Sources */,
AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */,
AB7BAB342012B519001E0872 /* geo_point_test.cc in Sources */,
5492E0AD2021552D00B64F25 /* FSTMemoryMutationQueueTests.mm in Sources */,
@@ -1454,10 +1517,12 @@
5492E054202154AB00B64F25 /* FIRFieldValueTests.mm in Sources */,
AB6B908620322E6D00CC290A /* maybe_document_test.cc in Sources */,
5492E09F2021552D00B64F25 /* FSTLevelDBMigrationsTests.mm in Sources */,
+ 546854AA20A36867004BDBD5 /* datastore_test.cc in Sources */,
5492E053202154AB00B64F25 /* FIRDocumentReferenceTests.mm in Sources */,
5492E09D2021552D00B64F25 /* FSTLocalStoreTests.mm in Sources */,
5492E0A32021552D00B64F25 /* FSTLocalSerializerTests.mm in Sources */,
5492E0A72021552D00B64F25 /* FSTLevelDBKeyTests.mm in Sources */,
+ B6FB4685208EA0F000554BA2 /* async_queue_std_test.cc in Sources */,
5492E0A22021552D00B64F25 /* FSTQueryCacheTests.mm in Sources */,
5492E0A52021552D00B64F25 /* FSTMemoryRemoteDocumentCacheTests.mm in Sources */,
AB6B908820322E8800CC290A /* no_document_test.cc in Sources */,
@@ -1467,12 +1532,15 @@
5492E0C82021557E00B64F25 /* FSTDatastoreTests.mm in Sources */,
54995F6F205B6E12004EFFA0 /* leveldb_key_test.cc in Sources */,
5492E065202154B900B64F25 /* FSTViewTests.mm in Sources */,
+ B6FB467D208E9D3C00554BA2 /* async_queue_test.cc in Sources */,
+ 54A0352F20A3B3D8003E0143 /* status_test.cc in Sources */,
5492E03C2021401F00B64F25 /* XCTestCase+Await.mm in Sources */,
B6152AD7202A53CB000E5744 /* document_key_test.cc in Sources */,
5467FB08203E6A44009C9584 /* app_testing.mm in Sources */,
54764FAF1FAA21B90085E60A /* FSTGoogleTestTests.mm in Sources */,
AB380D04201BC6E400D97691 /* ordered_code_test.cc in Sources */,
5492E03F2021401F00B64F25 /* FSTHelpers.mm in Sources */,
+ 549CCA5220A36DBC00BCEB75 /* sorted_map_test.cc in Sources */,
5492E068202154B900B64F25 /* FSTQueryTests.mm in Sources */,
5492E0AB2021552D00B64F25 /* StringViewTests.mm in Sources */,
5492E0C92021557E00B64F25 /* FSTRemoteEventTests.mm in Sources */,
@@ -1481,6 +1549,7 @@
ABF6506C201131F8005F2C74 /* timestamp_test.cc in Sources */,
5492E0AE2021552D00B64F25 /* FSTLevelDBQueryCacheTests.mm in Sources */,
ABC1D7DC2023A04B00BA84F0 /* credentials_provider_test.cc in Sources */,
+ 54511E8E209805F8005BD28F /* hashing_test.cc in Sources */,
5492E059202154AB00B64F25 /* FIRQuerySnapshotTests.mm in Sources */,
5492E050202154AA00B64F25 /* FIRCollectionReferenceTests.mm in Sources */,
ABA495BB202B7E80008A7851 /* snapshot_version_test.cc in Sources */,
@@ -1489,13 +1558,16 @@
5492E03420213FFC00B64F25 /* FSTMemorySpecTests.mm in Sources */,
AB380D02201BC69F00D97691 /* bits_test.cc in Sources */,
548DB929200D59F600E00ABC /* comparison_test.cc in Sources */,
+ 549CCA5720A36E1F00BCEB75 /* field_mask_test.cc in Sources */,
5492E03D2021401F00B64F25 /* FSTAssertTests.mm in Sources */,
AB38D93020236E21000A432D /* database_info_test.cc in Sources */,
5492E052202154AB00B64F25 /* FIRGeoPointTests.mm in Sources */,
5492E0C72021557E00B64F25 /* FSTSerializerBetaTests.mm in Sources */,
+ B6FB4690208F9BB300554BA2 /* executor_test.cc in Sources */,
5492E03520213FFC00B64F25 /* FSTSpecTests.mm in Sources */,
5492E057202154AB00B64F25 /* FIRSnapshotMetadataTests.mm in Sources */,
54740A571FC914BA00713A1A /* secure_random_test.cc in Sources */,
+ 549CCA5020A36DBC00BCEB75 /* sorted_set_test.cc in Sources */,
5492E0BE2021555100B64F25 /* FSTMutationTests.mm in Sources */,
132E3E53179DE287D875F3F2 /* FSTLevelDBTransactionTests.mm in Sources */,
);
@@ -1605,7 +1677,7 @@
/* Begin XCBuildConfiguration section */
54C9EDF82040E16300A969CD /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 9837221D251B8D40E7D7B454 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */;
+ baseConfigurationReference = 69E6C311558EC77729A16CF1 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -1644,7 +1716,7 @@
};
54C9EDF92040E16300A969CD /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 80025E2E892B94823962D11D /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.release.xcconfig */;
+ baseConfigurationReference = 11984BA0A99D7A7ABA5B0D90 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -1758,7 +1830,7 @@
};
6003F5C0195388D20070C39A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 555ACD6970390DC825A8F141 /* Pods-Firestore_Example_iOS.debug.xcconfig */;
+ baseConfigurationReference = 3C81DE3772628FE297055662 /* Pods-Firestore_Example_iOS.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
@@ -1768,8 +1840,12 @@
"\"${PODS_ROOT}/Firebase/Firebase/Firebase\"",
"\"${PODS_ROOT}/leveldb-library/include\"",
);
- INFOPLIST_FILE = "Firestore/Firestore-Info.plist";
+ INFOPLIST_FILE = "App/iOS/Firestore-Info.plist";
MODULE_NAME = ExampleApp;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
@@ -1778,7 +1854,7 @@
};
6003F5C1195388D20070C39A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = D8CA124A5F9A704EA0E12785 /* Pods-Firestore_Example_iOS.release.xcconfig */;
+ baseConfigurationReference = 3F0992A4B83C60841C52E960 /* Pods-Firestore_Example_iOS.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
@@ -1788,8 +1864,12 @@
"\"${PODS_ROOT}/Firebase/Firebase/Firebase\"",
"\"${PODS_ROOT}/leveldb-library/include\"",
);
- INFOPLIST_FILE = "Firestore/Firestore-Info.plist";
+ INFOPLIST_FILE = "App/iOS/Firestore-Info.plist";
MODULE_NAME = ExampleApp;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
@@ -1798,7 +1878,7 @@
};
6003F5C3195388D20070C39A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = BE88081EE627C46349C918EF /* Pods-Firestore_Tests_iOS.debug.xcconfig */;
+ baseConfigurationReference = E592181BFD7C53C305123739 /* Pods-Firestore_Tests_iOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = EQHXZ8M8AV;
@@ -1818,8 +1898,9 @@
"$(inherited)",
"\"${PODS_ROOT}/../../..\"",
"\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"",
- "\"${PODS_ROOT}/leveldb-library/include\"",
+ "\"${PODS_ROOT}/GoogleTest/googlemock/include\"",
"\"${PODS_ROOT}/GoogleTest/googletest/include\"",
+ "\"${PODS_ROOT}/leveldb-library/include\"",
);
INFOPLIST_FILE = "Tests/Tests-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
@@ -1831,7 +1912,7 @@
};
6003F5C4195388D20070C39A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 5A3E3BE5F322D66EE3D6CB65 /* Pods-Firestore_Tests_iOS.release.xcconfig */;
+ baseConfigurationReference = B3F5B3AAE791A5911B9EAA82 /* Pods-Firestore_Tests_iOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = EQHXZ8M8AV;
@@ -1851,8 +1932,9 @@
"$(inherited)",
"\"${PODS_ROOT}/../../..\"",
"\"${PODS_ROOT}/../../../Firestore/third_party/abseil-cpp\"",
- "\"${PODS_ROOT}/leveldb-library/include\"",
+ "\"${PODS_ROOT}/GoogleTest/googlemock/include\"",
"\"${PODS_ROOT}/GoogleTest/googletest/include\"",
+ "\"${PODS_ROOT}/leveldb-library/include\"",
);
INFOPLIST_FILE = "Tests/Tests-Info.plist";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
@@ -1864,7 +1946,7 @@
};
DE03B2E71F2149D600A30B9C /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 3C7CE22C50805C4A854C73A1 /* Pods-Firestore_IntegrationTests_iOS.debug.xcconfig */;
+ baseConfigurationReference = 1277F98C20D2DF0867496976 /* Pods-Firestore_IntegrationTests_iOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = EQHXZ8M8AV;
@@ -1891,10 +1973,6 @@
OTHER_LDFLAGS = (
"$(inherited)",
"-l\"c++\"",
- "-framework",
- "\"OCMock\"",
- "-framework",
- "\"leveldb\"",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1905,7 +1983,7 @@
};
DE03B2E81F2149D600A30B9C /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 618AC3C38A174084B9420162 /* Pods-Firestore_IntegrationTests_iOS.release.xcconfig */;
+ baseConfigurationReference = F354C0FE92645B56A6C6FD44 /* Pods-Firestore_IntegrationTests_iOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
DEVELOPMENT_TEAM = EQHXZ8M8AV;
@@ -1932,10 +2010,6 @@
OTHER_LDFLAGS = (
"$(inherited)",
"-l\"c++\"",
- "-framework",
- "\"OCMock\"",
- "-framework",
- "\"leveldb\"",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1946,7 +2020,7 @@
};
DE0761F31F2FE611003233AF /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 42491D7DC8C8CD245CC22B93 /* Pods-SwiftBuildTest.debug.xcconfig */;
+ baseConfigurationReference = 05C3D82261C3BE976889FF09 /* Pods-Firestore_Example_iOS-SwiftBuildTest.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -1959,7 +2033,7 @@
DEVELOPMENT_TEAM = EQHXZ8M8AV;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
- INFOPLIST_FILE = "Firestore/Firestore-Info.plist";
+ INFOPLIST_FILE = "App/iOS/Firestore-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = YES;
@@ -1973,7 +2047,7 @@
};
DE0761F41F2FE611003233AF /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = F23325524BEAF8D24F78AC88 /* Pods-SwiftBuildTest.release.xcconfig */;
+ baseConfigurationReference = 74ACEC3603BE58B57A7E8D4C /* Pods-Firestore_Example_iOS-SwiftBuildTest.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ANALYZER_NONNULL = YES;
@@ -1987,7 +2061,7 @@
DEVELOPMENT_TEAM = EQHXZ8M8AV;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
- INFOPLIST_FILE = "Firestore/Firestore-Info.plist";
+ INFOPLIST_FILE = "App/iOS/Firestore-Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 10.3;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MTL_ENABLE_DEBUG_INFO = NO;
diff --git a/Firestore/Example/Podfile b/Firestore/Example/Podfile
index a7fa097..7844066 100644
--- a/Firestore/Example/Podfile
+++ b/Firestore/Example/Podfile
@@ -2,21 +2,20 @@
#source 'sso://cpdc-internal/spec'
#source 'https://github.com/CocoaPods/Specs.git'
-# The next line is the forcing function for the Firebase pod. The Firebase
-# version's subspecs should depend on the component versions in their
-# corresponding podspec's.
-
-pod 'Firebase/Core', '5.0.1'
-
use_frameworks!
-pod 'FirebaseAuth', :path => '../../'
-pod 'FirebaseCore', :path => '../../'
-pod 'FirebaseFirestore', :path => '../../'
-
target 'Firestore_Example_iOS' do
platform :ios, '8.0'
+ # The next line is the forcing function for the Firebase pod. The Firebase
+ # version's subspecs should depend on the component versions in their
+ # corresponding podspec's.
+ pod 'Firebase/Core', '5.0.1'
+
+ pod 'FirebaseAuth', :path => '../../'
+ pod 'FirebaseCore', :path => '../../'
+ pod 'FirebaseFirestore', :path => '../../'
+
target 'Firestore_Tests_iOS' do
inherit! :search_paths
@@ -34,8 +33,8 @@ target 'Firestore_Example_iOS' do
target 'Firestore_SwiftTests_iOS' do
pod 'FirebaseFirestoreSwift', :path => '../../'
end
-end
-target 'SwiftBuildTest' do
- platform :ios, '8.0'
+ target 'SwiftBuildTest' do
+ platform :ios, '8.0'
+ end
end
diff --git a/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
index 0454152..5629075 100644
--- a/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
+++ b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
@@ -15,6 +15,7 @@
*/
#import <XCTest/XCTest.h>
+#include <memory>
#import "Firestore/Source/Core/FSTEventManager.h"
#import "Firestore/Source/Core/FSTQuery.h"
@@ -23,21 +24,29 @@
#import "Firestore/Source/Model/FSTDocumentSet.h"
#import "Firestore/Source/Remote/FSTRemoteEvent.h"
#import "Firestore/Source/Util/FSTAsyncQueryListener.h"
-#import "Firestore/Source/Util/FSTDispatchQueue.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+#include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h"
+#include "absl/memory/memory.h"
+
+using firebase::firestore::util::internal::ExecutorLibdispatch;
+using firebase::firestore::model::DocumentKeySet;
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTQueryListenerTests : XCTestCase
-@property(nonatomic, strong, readonly) FSTDispatchQueue *asyncQueue;
@end
-@implementation FSTQueryListenerTests
+@implementation FSTQueryListenerTests {
+ std::unique_ptr<ExecutorLibdispatch> _executor;
+}
- (void)setUp {
- _asyncQueue = [FSTDispatchQueue
- queueWith:dispatch_queue_create("FSTQueryListenerTests Queue", DISPATCH_QUEUE_SERIAL)];
+ // TODO(varconst): moving this test to C++, it should be possible to store Executor as a value,
+ // not a pointer, and initialize it in the constructor.
+ _executor = absl::make_unique<ExecutorLibdispatch>(
+ dispatch_queue_create("FSTQueryListenerTests Queue", DISPATCH_QUEUE_SERIAL));
}
- (void)testRaisesCollectionEvents {
@@ -53,7 +62,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryListener *listener = [self listenToQuery:query accumulatingSnapshots:accum];
FSTQueryListener *otherListener = [self listenToQuery:query accumulatingSnapshots:otherAccum];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[ doc1, doc2 ], nil);
FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[ doc2prime ], nil);
@@ -107,7 +116,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryListener *listener = [self listenToQuery:query accumulatingSnapshots:accum];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[], nil);
FSTTargetChange *ackTarget =
@@ -129,14 +138,14 @@ NS_ASSUME_NONNULL_BEGIN
FSTDocument *doc1 = FSTTestDoc("rooms/Eros", 3, @{@"name" : @"Eros"}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/Eros", 4, @{@"name" : @"Eros2"}, NO);
- __block FSTAsyncQueryListener *listener = [[FSTAsyncQueryListener alloc]
- initWithDispatchQueue:self.asyncQueue
- snapshotHandler:^(FSTViewSnapshot *snapshot, NSError *error) {
- [accum addObject:snapshot];
- [listener mute];
- }];
+ __block FSTAsyncQueryListener *listener =
+ [[FSTAsyncQueryListener alloc] initWithExecutor:_executor.get()
+ snapshotHandler:^(FSTViewSnapshot *snapshot, NSError *error) {
+ [accum addObject:snapshot];
+ [listener mute];
+ }];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *viewSnapshot1 = FSTTestApplyChanges(view, @[ doc1 ], nil);
FSTViewSnapshot *viewSnapshot2 = FSTTestApplyChanges(view, @[ doc2 ], nil);
@@ -146,9 +155,7 @@ NS_ASSUME_NONNULL_BEGIN
// Drain queue
XCTestExpectation *expectation = [self expectationWithDescription:@"Queue drained"];
- [self.asyncQueue dispatchAsync:^{
- [expectation fulfill];
- }];
+ _executor->Execute([=] { [expectation fulfill]; });
[self waitForExpectationsWithTimeout:4.0
handler:^(NSError *_Nullable expectationError) {
@@ -178,7 +185,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryListener *fullListener =
[self listenToQuery:query options:options accumulatingSnapshots:fullAccum];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[ doc1 ], nil);
FSTTargetChange *ackTarget =
@@ -218,7 +225,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryListener *fullListener =
[self listenToQuery:query options:options accumulatingSnapshots:fullAccum];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[ doc1, doc2 ], nil);
FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[ doc1Prime ], nil);
FSTViewSnapshot *snap3 = FSTTestApplyChanges(view, @[ doc3 ], nil);
@@ -265,7 +272,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryListener *fullListener =
[self listenToQuery:query options:options accumulatingSnapshots:fullAccum];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[ doc1, doc2 ], nil);
FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[ doc1Prime ], nil);
FSTViewSnapshot *snap3 = FSTTestApplyChanges(view, @[ doc3 ], nil);
@@ -298,7 +305,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryListener *filteredListener =
[self listenToQuery:query accumulatingSnapshots:filteredAccum];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[ doc1, doc2 ], nil);
FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[ doc1Prime, doc3 ], nil);
@@ -331,7 +338,7 @@ NS_ASSUME_NONNULL_BEGIN
waitForSyncWhenOnline:YES]
accumulatingSnapshots:events];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[ doc1 ], nil);
FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[ doc2 ], nil);
FSTViewSnapshot *snap3 =
@@ -374,7 +381,7 @@ NS_ASSUME_NONNULL_BEGIN
waitForSyncWhenOnline:YES]
accumulatingSnapshots:events];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[ doc1 ], nil);
FSTViewSnapshot *snap2 = FSTTestApplyChanges(view, @[ doc2 ], nil);
@@ -415,7 +422,7 @@ NS_ASSUME_NONNULL_BEGIN
options:[FSTListenOptions defaultOptions]
accumulatingSnapshots:events];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[], nil);
[listener applyChangedOnlineState:FSTOnlineStateOnline]; // no event
@@ -441,7 +448,7 @@ NS_ASSUME_NONNULL_BEGIN
options:[FSTListenOptions defaultOptions]
accumulatingSnapshots:events];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snap1 = FSTTestApplyChanges(view, @[], nil);
[listener applyChangedOnlineState:FSTOnlineStateOffline]; // no event
diff --git a/Firestore/Example/Tests/Core/FSTViewTests.mm b/Firestore/Example/Tests/Core/FSTViewTests.mm
index 63ce711..ec62d82 100644
--- a/Firestore/Example/Tests/Core/FSTViewTests.mm
+++ b/Firestore/Example/Tests/Core/FSTViewTests.mm
@@ -34,6 +34,7 @@
namespace testutil = firebase::firestore::testutil;
using firebase::firestore::model::ResourcePath;
+using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
@@ -49,7 +50,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testAddsDocumentsBasedOnQuery {
FSTQuery *query = [self queryForMessages];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
@@ -77,7 +78,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRemovesDocuments {
FSTQuery *query = [self queryForMessages];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
@@ -108,7 +109,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testReturnsNilIfThereAreNoChanges {
FSTQuery *query = [self queryForMessages];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
@@ -123,7 +124,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testDoesNotReturnNilForFirstChanges {
FSTQuery *query = [self queryForMessages];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewSnapshot *snapshot = FSTTestApplyChanges(view, @[], nil);
XCTAssertNotNil(snapshot);
@@ -137,7 +138,7 @@ NS_ASSUME_NONNULL_BEGIN
value:[FSTDoubleValue doubleValue:2]];
query = [query queryByAddingFilter:filter];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{ @"sort" : @1 }, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{ @"sort" : @2 }, NO);
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, @{ @"sort" : @3 }, NO);
@@ -169,7 +170,7 @@ NS_ASSUME_NONNULL_BEGIN
value:[FSTDoubleValue doubleValue:2]];
query = [query queryByAddingFilter:filter];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{ @"sort" : @1 }, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{ @"sort" : @3 }, NO);
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/3", 0, @{ @"sort" : @2 }, NO);
@@ -205,7 +206,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testRemovesDocumentsForQueryWithLimit {
FSTQuery *query = [self queryForMessages];
query = [query queryBySettingLimit:2];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{@"text" : @"msg1"}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{@"text" : @"msg2"}, NO);
@@ -239,7 +240,7 @@ NS_ASSUME_NONNULL_BEGIN
query = [query queryByAddingSortOrder:[FSTSortOrder sortOrderWithFieldPath:testutil::Field("num")
ascending:YES]];
query = [query queryBySettingLimit:2];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/1", 0, @{ @"num" : @1 }, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/2", 0, @{ @"num" : @2 }, NO);
@@ -283,7 +284,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testKeepsTrackOfLimboDocuments {
FSTQuery *query = [self queryForMessages];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
@@ -339,8 +340,8 @@ NS_ASSUME_NONNULL_BEGIN
// Unlike other cases, here the view is initialized with a set of previously synced documents
// which happens when listening to a previously listened-to query.
- FSTView *view = [[FSTView alloc] initWithQuery:query
- remoteDocuments:FSTTestDocKeySet(@[ doc1.key, doc2.key ])];
+ FSTView *view =
+ [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{doc1.key, doc2.key}];
FSTTargetChange *markCurrent =
[FSTTargetChange changeWithDocuments:@[]
@@ -361,7 +362,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
@@ -394,7 +395,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{ @"order" : @1 }, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{ @"order" : @2 }, NO);
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{ @"order" : @3 }, NO);
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
@@ -430,7 +431,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{ @"order" : @3 }, NO);
FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/3", 0, @{ @"order" : @4 }, NO);
FSTDocument *doc5 = FSTTestDoc("rooms/eros/messages/4", 0, @{ @"order" : @5 }, NO);
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
@@ -460,7 +461,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{ @"order" : @3 }, NO);
FSTDocument *doc4 = FSTTestDoc("rooms/eros/messages/3", 0, @{ @"order" : @4 }, NO);
FSTDocument *doc5 = FSTTestDoc("rooms/eros/messages/4", 0, @{ @"order" : @5 }, NO);
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
@@ -483,7 +484,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
@@ -506,7 +507,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *query = [[self queryForMessages] queryBySettingLimit:20];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
FSTViewDocumentChanges *changes =
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
@@ -528,7 +529,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *query = [[self queryForMessages] queryBySettingLimit:2];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
@@ -551,69 +552,69 @@ NS_ASSUME_NONNULL_BEGIN
FSTQuery *query = [self queryForMessages];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
[view applyChangesToDocuments:changes];
- XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[]));
+ XCTAssertEqual(changes.mutatedKeys, DocumentKeySet{});
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, YES);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
- XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc3.key ]));
+ XCTAssertEqual(changes.mutatedKeys, DocumentKeySet{doc3.key});
}
- (void)testRemovesKeysFromMutatedKeysWhenNewDocHasNoLocalChanges {
FSTQuery *query = [self queryForMessages];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, YES);
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
[view applyChangesToDocuments:changes];
- XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
+ XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
FSTDocument *doc2Prime = FSTTestDoc("rooms/eros/messages/1", 0, @{}, NO);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc2Prime ])];
[view applyChangesToDocuments:changes];
- XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[]));
+ XCTAssertEqual(changes.mutatedKeys, DocumentKeySet{});
}
- (void)testRemembersLocalMutationsFromPreviousSnapshot {
FSTQuery *query = [self queryForMessages];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, YES);
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
[view applyChangesToDocuments:changes];
- XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
+ XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, NO);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ])];
[view applyChangesToDocuments:changes];
- XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
+ XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
}
- (void)testRemembersLocalMutationsFromPreviousCallToComputeChangesWithDocuments {
FSTQuery *query = [self queryForMessages];
FSTDocument *doc1 = FSTTestDoc("rooms/eros/messages/0", 0, @{}, NO);
FSTDocument *doc2 = FSTTestDoc("rooms/eros/messages/1", 0, @{}, YES);
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:[FSTDocumentKeySet keySet]];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:DocumentKeySet{}];
// Start with a full view.
FSTViewDocumentChanges *changes =
[view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc1, doc2 ])];
- XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
+ XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
FSTDocument *doc3 = FSTTestDoc("rooms/eros/messages/2", 0, @{}, NO);
changes = [view computeChangesWithDocuments:FSTTestDocUpdates(@[ doc3 ]) previousChanges:changes];
- XCTAssertEqualObjects(changes.mutatedKeys, FSTTestDocKeySet(@[ doc2.key ]));
+ XCTAssertEqual(changes.mutatedKeys, (DocumentKeySet{doc2.key}));
}
@end
diff --git a/Firestore/Example/Tests/GoogleTest/GoogleTest.podspec b/Firestore/Example/Tests/GoogleTest/GoogleTest.podspec
index 064fc59..0ecba3d 100644
--- a/Firestore/Example/Tests/GoogleTest/GoogleTest.podspec
+++ b/Firestore/Example/Tests/GoogleTest/GoogleTest.podspec
@@ -41,6 +41,8 @@ Google's C++ test framework.
# (e.g. gtest.h). We don't need them because they're effectively empty:
# they're compile-time hooks for third-party customization that we don't use.
s.public_header_files = [
+ 'googlemock/include/gmock/*.h',
+ 'googlemock/include/gmock/internal/*.h',
'googletest/include/gtest/*.h',
'googletest/include/gtest/internal/*.h'
]
@@ -54,6 +56,9 @@ Google's C++ test framework.
]
s.source_files = [
+ 'googlemock/src/*.cc',
+ 'googlemock/include/gmock/*.h',
+ 'googlemock/include/gmock/internal/*.h',
'googletest/src/*.cc',
'googletest/include/gtest/*.h',
'googletest/include/gtest/internal/*.h'
@@ -61,8 +66,11 @@ Google's C++ test framework.
s.exclude_files = [
# A convenience wrapper for a simple command-line build. If included in
- # this build, results in duplicate symbols
+ # this build, results in duplicate symbols.
+ 'googlemock/src/gmock-all.cc',
'googletest/src/gtest-all.cc',
+ # Both gmock and gtest define a main function but we only need one.
+ 'googletest/src/gtest_main.cc',
]
s.library = 'c++'
@@ -70,12 +78,17 @@ Google's C++ test framework.
# When building this pod there are headers in googletest/src.
s.pod_target_xcconfig = {
'HEADER_SEARCH_PATHS' =>
- '"${PODS_ROOT}/GoogleTest/googletest/include" "${PODS_ROOT}/GoogleTest/googletest"'
+ '"${PODS_ROOT}/GoogleTest/googlemock/include" ' +
+ '"${PODS_ROOT}/GoogleTest/googletest/include" ' +
+ '"${PODS_ROOT}/GoogleTest/googletest"'
}
s.prepare_command = <<-'CMD'
# Remove includes of files in internal/custom
sed -i.bak -e '/include.*internal\/custom/ d' \
+ googlemock/include/gmock/gmock-matchers.h \
+ googlemock/include/gmock/gmock-generated-actions.h \
+ googlemock/include/gmock/internal/gmock-port.h \
googletest/include/gtest/gtest-printers.h \
googletest/include/gtest/internal/gtest-port.h \
googletest/src/gtest-death-test.cc \
diff --git a/Firestore/Example/Tests/Integration/API/FIRArrayTransformTests.mm b/Firestore/Example/Tests/Integration/API/FIRArrayTransformTests.mm
index 1c82461..0a5b413 100644
--- a/Firestore/Example/Tests/Integration/API/FIRArrayTransformTests.mm
+++ b/Firestore/Example/Tests/Integration/API/FIRArrayTransformTests.mm
@@ -36,7 +36,7 @@
FIRDocumentReference *_docRef;
// Accumulator used to capture events during the test.
- FSTEventAccumulator *_accumulator;
+ FSTEventAccumulator<FIRDocumentSnapshot *> *_accumulator;
// Listener registration for a listener maintained during the course of the test.
id<FIRListenerRegistration> _listenerRegistration;
@@ -64,29 +64,11 @@
#pragma mark - Test Helpers
-/** Waits for a snapshot with local writes. */
-- (FIRDocumentSnapshot *)waitForLocalEvent {
- FIRDocumentSnapshot *snapshot;
- do {
- snapshot = [_accumulator awaitEventWithName:@"Local event."];
- } while (!snapshot.metadata.hasPendingWrites);
- return snapshot;
-}
-
-/** Waits for a snapshot that has no pending writes */
-- (FIRDocumentSnapshot *)waitForRemoteEvent {
- FIRDocumentSnapshot *snapshot;
- do {
- snapshot = [_accumulator awaitEventWithName:@"Remote event."];
- } while (snapshot.metadata.hasPendingWrites);
- return snapshot;
-}
-
/** Writes some initial data and consumes the events generated. */
- (void)writeInitialData:(NSDictionary<NSString *, id> *)data {
[self writeDocumentRef:_docRef data:data];
- XCTAssertEqualObjects([self waitForLocalEvent].data, data);
- XCTAssertEqualObjects([self waitForRemoteEvent].data, data);
+ XCTAssertEqualObjects([_accumulator awaitLocalEvent].data, data);
+ XCTAssertEqualObjects([_accumulator awaitRemoteEvent].data, data);
}
#pragma mark - Test Cases
@@ -97,8 +79,8 @@
@"array" : [FIRFieldValue fieldValueForArrayUnion:@[ @1, @2 ]]
}];
id expected = @{ @"array" : @[ @1, @2 ] };
- XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
- XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitLocalEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitRemoteEvent].data, expected);
}
- (void)testAppendToArrayViaUpdate {
@@ -110,8 +92,8 @@
}];
id expected = @{ @"array" : @[ @1, @3, @2, @4 ] };
- XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
- XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitLocalEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitRemoteEvent].data, expected);
}
- (void)testAppendToArrayViaMergeSet {
@@ -123,8 +105,8 @@
}];
id expected = @{ @"array" : @[ @1, @3, @2, @4 ] };
- XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
- XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitLocalEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitRemoteEvent].data, expected);
}
- (void)testAppendObjectToArrayViaUpdate {
@@ -137,8 +119,8 @@
}];
id expected = @{ @"array" : @[ @{@"a" : @"hi"}, @{@"a" : @"bye"} ] };
- XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
- XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitLocalEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitRemoteEvent].data, expected);
}
- (void)testRemoveFromArrayViaUpdate {
@@ -150,8 +132,8 @@
}];
id expected = @{ @"array" : @[ @3, @3 ] };
- XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
- XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitLocalEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitRemoteEvent].data, expected);
}
- (void)testRemoveFromArrayViaMergeSet {
@@ -163,8 +145,8 @@
}];
id expected = @{ @"array" : @[ @3, @3 ] };
- XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
- XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitLocalEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitRemoteEvent].data, expected);
}
- (void)testRemoveObjectFromArrayViaUpdate {
@@ -176,8 +158,8 @@
}];
id expected = @{ @"array" : @[ @{@"a" : @"bye"} ] };
- XCTAssertEqualObjects([self waitForLocalEvent].data, expected);
- XCTAssertEqualObjects([self waitForRemoteEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitLocalEvent].data, expected);
+ XCTAssertEqualObjects([_accumulator awaitRemoteEvent].data, expected);
}
@end
diff --git a/Firestore/Example/Tests/Integration/API/FIRCursorTests.mm b/Firestore/Example/Tests/Integration/API/FIRCursorTests.mm
index 2188b8a..e444d7a 100644
--- a/Firestore/Example/Tests/Integration/API/FIRCursorTests.mm
+++ b/Firestore/Example/Tests/Integration/API/FIRCursorTests.mm
@@ -216,32 +216,29 @@ FIRTimestamp *TimestampWithMicros(int64_t seconds, int32_t micros) {
}
- (void)testTimestampsCanBePassedToQueriesInWhereClause {
- FIRTimestamp *currentTimestamp = [FIRTimestamp timestamp];
- int64_t seconds = currentTimestamp.seconds;
- int32_t micros = currentTimestamp.nanoseconds / 1000;
FIRCollectionReference *testCollection = [self collectionRefWithDocuments:@{
@"a" : @{
- @"timestamp" : TimestampWithMicros(seconds, micros + 2),
+ @"timestamp" : TimestampWithMicros(100, 7),
},
@"b" : @{
- @"timestamp" : TimestampWithMicros(seconds, micros - 1),
+ @"timestamp" : TimestampWithMicros(100, 4),
},
@"c" : @{
- @"timestamp" : TimestampWithMicros(seconds, micros + 3),
+ @"timestamp" : TimestampWithMicros(100, 8),
},
@"d" : @{
- @"timestamp" : TimestampWithMicros(seconds, micros),
+ @"timestamp" : TimestampWithMicros(100, 5),
},
@"e" : @{
- @"timestamp" : TimestampWithMicros(seconds, micros + 1),
+ @"timestamp" : TimestampWithMicros(100, 6),
}
}];
- FIRQuerySnapshot *querySnapshot = [self
- readDocumentSetForRef:[[testCollection queryWhereField:@"timestamp"
- isGreaterThanOrEqualTo:TimestampWithMicros(seconds, micros)]
- queryWhereField:@"timestamp"
- isLessThan:TimestampWithMicros(seconds, micros + 3)]];
+ FIRQuerySnapshot *querySnapshot =
+ [self readDocumentSetForRef:[[testCollection queryWhereField:@"timestamp"
+ isGreaterThanOrEqualTo:TimestampWithMicros(100, 5)]
+ queryWhereField:@"timestamp"
+ isLessThan:TimestampWithMicros(100, 8)]];
XCTAssertEqualObjects(FIRQuerySnapshotGetIDs(querySnapshot), (@[ @"d", @"e", @"a" ]));
}
diff --git a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm
index 9b6febe..f8091c0 100644
--- a/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm
+++ b/Firestore/Example/Tests/Integration/API/FIRDatabaseTests.mm
@@ -256,6 +256,155 @@
XCTAssertEqualObjects(document.data, finalData);
}
+- (void)testCannotSpecifyFieldMaskForMissingField {
+ FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
+
+ XCTAssertThrowsSpecific(
+ { [doc setData:@{} mergeFields:@[ @"foo" ]]; }, NSException,
+ @"Field 'foo' is specified in your field mask but missing from your input data.");
+}
+
+- (void)testCanSetASubsetOfFieldsUsingMask {
+ FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
+
+ NSDictionary<NSString *, id> *initialData =
+ @{ @"desc" : @"Description",
+ @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"} };
+
+ NSDictionary<NSString *, id> *finalData = @{@"desc" : @"Description", @"owner" : @"Sebastian"};
+
+ [self writeDocumentRef:doc data:initialData];
+
+ XCTestExpectation *completed =
+ [self expectationWithDescription:@"testCanSetASubsetOfFieldsUsingMask"];
+
+ [doc setData:@{@"desc" : @"NewDescription", @"owner" : @"Sebastian"}
+ mergeFields:@[ @"owner" ]
+ completion:^(NSError *error) {
+ XCTAssertNil(error);
+ [completed fulfill];
+ }];
+
+ [self awaitExpectations];
+
+ FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
+ XCTAssertEqualObjects(document.data, finalData);
+}
+
+- (void)testDoesNotApplyFieldDeleteOutsideOfMask {
+ FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
+
+ NSDictionary<NSString *, id> *initialData =
+ @{ @"desc" : @"Description",
+ @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"} };
+
+ NSDictionary<NSString *, id> *finalData = @{@"desc" : @"Description", @"owner" : @"Sebastian"};
+
+ [self writeDocumentRef:doc data:initialData];
+
+ XCTestExpectation *completed =
+ [self expectationWithDescription:@"testCanSetASubsetOfFieldsUsingMask"];
+
+ [doc setData:@{@"desc" : [FIRFieldValue fieldValueForDelete], @"owner" : @"Sebastian"}
+ mergeFields:@[ @"owner" ]
+ completion:^(NSError *error) {
+ XCTAssertNil(error);
+ [completed fulfill];
+ }];
+
+ [self awaitExpectations];
+
+ FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
+ XCTAssertEqualObjects(document.data, finalData);
+}
+
+- (void)testDoesNotApplyFieldTransformOutsideOfMask {
+ FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
+
+ NSDictionary<NSString *, id> *initialData =
+ @{ @"desc" : @"Description",
+ @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"} };
+
+ NSDictionary<NSString *, id> *finalData = @{@"desc" : @"Description", @"owner" : @"Sebastian"};
+
+ [self writeDocumentRef:doc data:initialData];
+
+ XCTestExpectation *completed =
+ [self expectationWithDescription:@"testCanSetASubsetOfFieldsUsingMask"];
+
+ [doc setData:@{@"desc" : [FIRFieldValue fieldValueForServerTimestamp], @"owner" : @"Sebastian"}
+ mergeFields:@[ @"owner" ]
+ completion:^(NSError *error) {
+ XCTAssertNil(error);
+ [completed fulfill];
+ }];
+
+ [self awaitExpectations];
+
+ FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
+ XCTAssertEqualObjects(document.data, finalData);
+}
+
+- (void)testCanSetEmptyFieldMask {
+ FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
+
+ NSDictionary<NSString *, id> *initialData =
+ @{ @"desc" : @"Description",
+ @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"} };
+
+ NSDictionary<NSString *, id> *finalData = initialData;
+
+ [self writeDocumentRef:doc data:initialData];
+
+ XCTestExpectation *completed =
+ [self expectationWithDescription:@"testCanSetASubsetOfFieldsUsingMask"];
+
+ [doc setData:@{@"desc" : [FIRFieldValue fieldValueForServerTimestamp], @"owner" : @"Sebastian"}
+ mergeFields:@[]
+ completion:^(NSError *error) {
+ XCTAssertNil(error);
+ [completed fulfill];
+ }];
+
+ [self awaitExpectations];
+
+ FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
+ XCTAssertEqualObjects(document.data, finalData);
+}
+
+- (void)testCanSpecifyFieldsMultipleTimesInFieldMask {
+ FIRDocumentReference *doc = [[self.db collectionWithPath:@"rooms"] documentWithAutoID];
+
+ NSDictionary<NSString *, id> *initialData =
+ @{ @"desc" : @"Description",
+ @"owner" : @{@"name" : @"Jonny", @"email" : @"abc@xyz.com"} };
+
+ NSDictionary<NSString *, id> *finalData = @{
+ @"desc" : @"Description",
+ @"owner" : @{@"name" : @"Sebastian", @"email" : @"new@xyz.com"}
+ };
+
+ [self writeDocumentRef:doc data:initialData];
+
+ XCTestExpectation *completed =
+ [self expectationWithDescription:@"testCanSetASubsetOfFieldsUsingMask"];
+
+ [doc setData:@{
+ @"desc" : @"NewDescription",
+ @"owner" : @{@"name" : @"Sebastian", @"email" : @"new@xyz.com"}
+ }
+ mergeFields:@[ @"owner.name", @"owner", @"owner" ]
+ completion:^(NSError *error) {
+ XCTAssertNil(error);
+ [completed fulfill];
+ }];
+
+ [self awaitExpectations];
+
+ FIRDocumentSnapshot *document = [self readDocumentForRef:doc];
+ XCTAssertEqualObjects(document.data, finalData);
+}
+
- (void)testAddingToACollectionYieldsTheCorrectDocumentReference {
FIRCollectionReference *coll = [self.db collectionWithPath:@"collection"];
FIRDocumentReference *ref = [coll addDocumentWithData:@{ @"foo" : @1 }];
diff --git a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm
index e1ad3d2..6a755c6 100644
--- a/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm
+++ b/Firestore/Example/Tests/Integration/API/FIRServerTimestampTests.mm
@@ -90,24 +90,6 @@
XCTAssertEqualObjects(initialDataSnap.data, _initialData);
}
-/** Waits for a snapshot with local writes. */
-- (FIRDocumentSnapshot *)waitForLocalEvent {
- FIRDocumentSnapshot *snapshot;
- do {
- snapshot = [_accumulator awaitEventWithName:@"Local event."];
- } while (!snapshot.metadata.hasPendingWrites);
- return snapshot;
-}
-
-/** Waits for a snapshot that has no pending writes */
-- (FIRDocumentSnapshot *)waitForRemoteEvent {
- FIRDocumentSnapshot *snapshot;
- do {
- snapshot = [_accumulator awaitEventWithName:@"Remote event."];
- } while (snapshot.metadata.hasPendingWrites);
- return snapshot;
-}
-
/** Verifies a snapshot containing _setData but with NSNull for the timestamps. */
- (void)verifyTimestampsAreNullInSnapshot:(FIRDocumentSnapshot *)snapshot {
XCTAssertEqualObjects(snapshot.data, [self expectedDataWithTimestamp:[NSNull null]]);
@@ -170,41 +152,42 @@
- (void)testServerTimestampsWorkViaSet {
[self writeDocumentRef:_docRef data:_setData];
- [self verifyTimestampsAreNullInSnapshot:[self waitForLocalEvent]];
- [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+ [self verifyTimestampsAreNullInSnapshot:[_accumulator awaitLocalEvent]];
+ [self verifySnapshotWithResolvedTimestamps:[_accumulator awaitRemoteEvent]];
}
- (void)testServerTimestampsWorkViaUpdate {
[self writeInitialData];
[self updateDocumentRef:_docRef data:_updateData];
- [self verifyTimestampsAreNullInSnapshot:[self waitForLocalEvent]];
- [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+ [self verifyTimestampsAreNullInSnapshot:[_accumulator awaitLocalEvent]];
+ [self verifySnapshotWithResolvedTimestamps:[_accumulator awaitRemoteEvent]];
}
- (void)testServerTimestampsWithEstimatedValue {
[self writeDocumentRef:_docRef data:_setData];
- [self verifyTimestampsAreEstimatedInSnapshot:[self waitForLocalEvent]];
- [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+ [self verifyTimestampsAreEstimatedInSnapshot:[_accumulator awaitLocalEvent]];
+ [self verifySnapshotWithResolvedTimestamps:[_accumulator awaitRemoteEvent]];
}
- (void)testServerTimestampsWithPreviousValue {
[self writeDocumentRef:_docRef data:_setData];
- [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
- FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
+ [self verifyTimestampsInSnapshot:[_accumulator awaitLocalEvent] fromPreviousSnapshot:nil];
+ FIRDocumentSnapshot *remoteSnapshot = [_accumulator awaitRemoteEvent];
[_docRef updateData:_updateData];
- [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:remoteSnapshot];
+ [self verifyTimestampsInSnapshot:[_accumulator awaitLocalEvent]
+ fromPreviousSnapshot:remoteSnapshot];
- [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+ [self verifySnapshotWithResolvedTimestamps:[_accumulator awaitRemoteEvent]];
}
- (void)testServerTimestampsWithPreviousValueOfDifferentType {
[self writeDocumentRef:_docRef data:_setData];
- [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
- [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+ [self verifyTimestampsInSnapshot:[_accumulator awaitLocalEvent] fromPreviousSnapshot:nil];
+ [self verifySnapshotWithResolvedTimestamps:[_accumulator awaitRemoteEvent]];
[_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
- FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent];
+ FIRDocumentSnapshot *localSnapshot = [_accumulator awaitLocalEvent];
XCTAssertEqualObjects([localSnapshot valueForField:@"a"], [NSNull null]);
XCTAssertEqualObjects(
[localSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorPrevious],
@@ -213,7 +196,7 @@
[[localSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorEstimate]
isKindOfClass:[FIRTimestamp class]]);
- FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
+ FIRDocumentSnapshot *remoteSnapshot = [_accumulator awaitRemoteEvent];
XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[FIRTimestamp class]]);
XCTAssertTrue([
[remoteSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorPrevious]
@@ -225,55 +208,55 @@
- (void)testServerTimestampsWithConsecutiveUpdates {
[self writeDocumentRef:_docRef data:_setData];
- [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
- [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+ [self verifyTimestampsInSnapshot:[_accumulator awaitLocalEvent] fromPreviousSnapshot:nil];
+ [self verifySnapshotWithResolvedTimestamps:[_accumulator awaitRemoteEvent]];
[self disableNetwork];
[_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
- FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent];
+ FIRDocumentSnapshot *localSnapshot = [_accumulator awaitLocalEvent];
XCTAssertEqualObjects(
[localSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorPrevious],
@42);
[_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
- localSnapshot = [self waitForLocalEvent];
+ localSnapshot = [_accumulator awaitLocalEvent];
XCTAssertEqualObjects(
[localSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorPrevious],
@42);
[self enableNetwork];
- FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
+ FIRDocumentSnapshot *remoteSnapshot = [_accumulator awaitRemoteEvent];
XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[FIRTimestamp class]]);
}
- (void)testServerTimestampsPreviousValueFromLocalMutation {
[self writeDocumentRef:_docRef data:_setData];
- [self verifyTimestampsInSnapshot:[self waitForLocalEvent] fromPreviousSnapshot:nil];
- [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+ [self verifyTimestampsInSnapshot:[_accumulator awaitLocalEvent] fromPreviousSnapshot:nil];
+ [self verifySnapshotWithResolvedTimestamps:[_accumulator awaitRemoteEvent]];
[self disableNetwork];
[_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
- FIRDocumentSnapshot *localSnapshot = [self waitForLocalEvent];
+ FIRDocumentSnapshot *localSnapshot = [_accumulator awaitLocalEvent];
XCTAssertEqualObjects(
[localSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorPrevious],
@42);
[_docRef updateData:@{ @"a" : @1337 }];
- localSnapshot = [self waitForLocalEvent];
+ localSnapshot = [_accumulator awaitLocalEvent];
XCTAssertEqualObjects([localSnapshot valueForField:@"a"], @1337);
[_docRef updateData:@{@"a" : [FIRFieldValue fieldValueForServerTimestamp]}];
- localSnapshot = [self waitForLocalEvent];
+ localSnapshot = [_accumulator awaitLocalEvent];
XCTAssertEqualObjects(
[localSnapshot valueForField:@"a" serverTimestampBehavior:FIRServerTimestampBehaviorPrevious],
@1337);
[self enableNetwork];
- FIRDocumentSnapshot *remoteSnapshot = [self waitForRemoteEvent];
+ FIRDocumentSnapshot *remoteSnapshot = [_accumulator awaitRemoteEvent];
XCTAssertTrue([[remoteSnapshot valueForField:@"a"] isKindOfClass:[FIRTimestamp class]]);
}
@@ -282,7 +265,7 @@
[transaction setData:_setData forDocument:_docRef];
}];
- [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+ [self verifySnapshotWithResolvedTimestamps:[_accumulator awaitRemoteEvent]];
}
- (void)testServerTimestampsWorkViaTransactionUpdate {
@@ -290,7 +273,7 @@
[self runTransactionBlock:^(FIRTransaction *transaction) {
[transaction updateData:_updateData forDocument:_docRef];
}];
- [self verifySnapshotWithResolvedTimestamps:[self waitForRemoteEvent]];
+ [self verifySnapshotWithResolvedTimestamps:[_accumulator awaitRemoteEvent]];
}
- (void)testServerTimestampsFailViaUpdateOnNonexistentDocument {
diff --git a/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm b/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm
index 6d10aba..8af8d15 100644
--- a/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm
+++ b/Firestore/Example/Tests/Integration/API/FIRValidationTests.mm
@@ -507,8 +507,8 @@
FSTAssertThrows([collection queryWhereFieldPath:[FIRFieldPath documentID] isEqualTo:@1], reason);
reason =
- @"Invalid query. You can't do arrayContains queries on document ID since document IDs are "
- @"not arrays.";
+ @"Invalid query. You can't perform arrayContains queries on document ID since document IDs "
+ "are not arrays.";
FSTAssertThrows([collection queryWhereFieldPath:[FIRFieldPath documentID] arrayContains:@1],
reason);
}
diff --git a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm
index ad911ce..920e3c5 100644
--- a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm
+++ b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm
@@ -25,7 +25,6 @@
#import "Firestore/Source/API/FSTUserDataConverter.h"
#import "Firestore/Source/Core/FSTFirestoreClient.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
diff --git a/Firestore/Example/Tests/Integration/FSTStreamTests.mm b/Firestore/Example/Tests/Integration/FSTStreamTests.mm
index 7e37913..2e5c9b6 100644
--- a/Firestore/Example/Tests/Integration/FSTStreamTests.mm
+++ b/Firestore/Example/Tests/Integration/FSTStreamTests.mm
@@ -29,12 +29,14 @@
#include "Firestore/core/src/firebase/firestore/auth/empty_credentials_provider.h"
#include "Firestore/core/src/firebase/firestore/core/database_info.h"
#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
namespace util = firebase::firestore::util;
using firebase::firestore::auth::EmptyCredentialsProvider;
using firebase::firestore::core::DatabaseInfo;
using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::SnapshotVersion;
/** Exposes otherwise private methods for testing. */
@interface FSTStream (Testing)
@@ -101,13 +103,13 @@ using firebase::firestore::model::DatabaseId;
}
- (void)watchStreamDidChange:(FSTWatchChange *)change
- snapshotVersion:(FSTSnapshotVersion *)snapshotVersion {
+ snapshotVersion:(const SnapshotVersion &)snapshotVersion {
[_states addObject:@"watchStreamDidChange"];
[_expectation fulfill];
_expectation = nil;
}
-- (void)writeStreamDidReceiveResponseWithVersion:(FSTSnapshotVersion *)commitVersion
+- (void)writeStreamDidReceiveResponseWithVersion:(const SnapshotVersion &)commitVersion
mutationResults:(NSArray<FSTMutationResult *> *)results {
[_states addObject:@"writeStreamDidReceiveResponseWithVersion"];
[_expectation fulfill];
diff --git a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm
index 362f46f..0e160c0 100644
--- a/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm
+++ b/Firestore/Example/Tests/Local/FSTLocalSerializerTests.mm
@@ -29,7 +29,6 @@
#import "Firestore/Protos/objc/google/firestore/v1beta1/Write.pbobjc.h"
#import "Firestore/Protos/objc/google/type/Latlng.pbobjc.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
@@ -50,6 +49,7 @@ namespace testutil = firebase::firestore::testutil;
using firebase::firestore::model::DatabaseId;
using firebase::firestore::model::FieldMask;
using firebase::firestore::model::Precondition;
+using firebase::firestore::model::SnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
@@ -125,7 +125,7 @@ NS_ASSUME_NONNULL_BEGIN
XCTAssertEqual(decoded.batchID, model.batchID);
XCTAssertEqualObjects(decoded.localWriteTime, model.localWriteTime);
XCTAssertEqualObjects(decoded.mutations, model.mutations);
- XCTAssertEqualObjects([decoded keys], [model keys]);
+ XCTAssertEqual([decoded keys], [model keys]);
}
- (void)testEncodesDocumentAsMaybeDocument {
@@ -162,7 +162,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testEncodesQueryData {
FSTQuery *query = FSTTestQuery("room");
FSTTargetID targetID = 42;
- FSTSnapshotVersion *version = FSTTestVersion(1039);
+ SnapshotVersion version = testutil::Version(1039);
NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(1039);
FSTQueryData *queryData = [[FSTQueryData alloc] initWithQuery:query
diff --git a/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm b/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm
index 3565e2e..e10fb12 100644
--- a/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm
+++ b/Firestore/Example/Tests/Local/FSTLocalStoreTests.mm
@@ -41,19 +41,15 @@
#import "Firestore/third_party/Immutable/Tests/FSTImmutableSortedSet+Testing.h"
#include "Firestore/core/src/firebase/firestore/auth/user.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+namespace testutil = firebase::firestore::testutil;
using firebase::firestore::auth::User;
+using firebase::firestore::model::SnapshotVersion;
+using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
-/** Creates a document version dictionary mapping the document in @a mutation to @a version. */
-FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
- FSTTestSnapshotVersion version) {
- FSTDocumentVersionDictionary *result = [FSTDocumentVersionDictionary documentVersionDictionary];
- result = [result dictionaryBySettingObject:FSTTestVersion(version) forKey:mutation.key];
- return result;
-}
-
@interface FSTLocalStoreTests ()
@property(nonatomic, strong, readwrite) id<FSTPersistence> localStorePersistence;
@@ -140,7 +136,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
FSTMutationBatch *batch = [self.batches firstObject];
[self.batches removeObjectAtIndex:0];
XCTAssertEqual(batch.mutations.count, 1, @"Acknowledging more than one mutation not supported.");
- FSTSnapshotVersion *version = FSTTestVersion(documentVersion);
+ SnapshotVersion version = testutil::Version(documentVersion);
FSTMutationResult *mutationResult =
[[FSTMutationResult alloc] initWithVersion:version transformResults:nil];
FSTMutationBatchResult *result = [FSTMutationBatchResult resultWithBatch:batch
@@ -227,8 +223,8 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
FSTMutationBatch *batch = [[FSTMutationBatch alloc] initWithBatchID:1
localWriteTime:[FIRTimestamp timestamp]
mutations:@[ set1, set2 ]];
- FSTDocumentKeySet *keys = [batch keys];
- XCTAssertEqual(keys.count, 2);
+ DocumentKeySet keys = [batch keys];
+ XCTAssertEqual(keys.size(), 2u);
}
- (void)testHandlesSetMutation {
@@ -805,6 +801,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
FSTQuery *query = FSTTestQuery("foo/bar");
FSTQueryData *queryData = [self.localStore allocateQuery:query];
+ FSTListenSequenceNumber initialSequenceNumber = queryData.sequenceNumber;
FSTBoxedTargetID *targetID = @(queryData.targetID);
NSData *resumeToken = FSTTestResumeTokenFromSnapshotVersion(1000);
@@ -818,7 +815,7 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
NSMutableDictionary<FSTBoxedTargetID *, NSNumber *> *pendingResponses =
[NSMutableDictionary dictionary];
FSTWatchChangeAggregator *aggregator =
- [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:FSTTestVersion(1000)
+ [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:testutil::Version(1000)
listenTargets:listens
pendingTargetResponses:pendingResponses];
[aggregator addWatchChanges:@[ watchChange ]];
@@ -831,6 +828,10 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
// Should come back with the same resume token
FSTQueryData *queryData2 = [self.localStore allocateQuery:query];
XCTAssertEqualObjects(queryData2.resumeToken, resumeToken);
+
+ // The sequence number should have been bumped when we saved the new resume token.
+ FSTListenSequenceNumber newSequenceNumber = queryData2.sequenceNumber;
+ XCTAssertGreaterThan(newSequenceNumber, initialSequenceNumber);
}
- (void)testRemoteDocumentKeysForTarget {
@@ -848,13 +849,14 @@ FSTDocumentVersionDictionary *FSTVersionDictionary(FSTMutation *mutation,
[self.localStore locallyWriteMutations:@[ FSTTestSetMutation(@"foo/bonk", @{@"a" : @"b"}) ]];
- FSTDocumentKeySet *keys = [self.localStore remoteDocumentKeysForTarget:2];
- FSTAssertEqualSets(keys, (@[ FSTTestDocKey(@"foo/bar"), FSTTestDocKey(@"foo/baz") ]));
+ DocumentKeySet keys = [self.localStore remoteDocumentKeysForTarget:2];
+ DocumentKeySet expected{testutil::Key("foo/bar"), testutil::Key("foo/baz")};
+ XCTAssertEqual(keys, expected);
[self restartWithNoopGarbageCollector];
keys = [self.localStore remoteDocumentKeysForTarget:2];
- FSTAssertEqualSets(keys, (@[ FSTTestDocKey(@"foo/bar"), FSTTestDocKey(@"foo/baz") ]));
+ XCTAssertEqual(keys, (DocumentKeySet{testutil::Key("foo/bar"), testutil::Key("foo/baz")}));
}
@end
diff --git a/Firestore/Example/Tests/Local/FSTQueryCacheTests.mm b/Firestore/Example/Tests/Local/FSTQueryCacheTests.mm
index 429a83a..44b49de 100644
--- a/Firestore/Example/Tests/Local/FSTQueryCacheTests.mm
+++ b/Firestore/Example/Tests/Local/FSTQueryCacheTests.mm
@@ -19,7 +19,6 @@
#include <set>
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
#import "Firestore/Source/Local/FSTPersistence.h"
#import "Firestore/Source/Local/FSTQueryData.h"
@@ -32,6 +31,8 @@
namespace testutil = firebase::firestore::testutil;
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::SnapshotVersion;
+using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
@@ -130,9 +131,9 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryData *result = [self.queryCache queryDataForQuery:_queryRooms];
XCTAssertNotEqualObjects(queryData2.resumeToken, queryData1.resumeToken);
- XCTAssertNotEqualObjects(queryData2.snapshotVersion, queryData1.snapshotVersion);
+ XCTAssertNotEqual(queryData2.snapshotVersion, queryData1.snapshotVersion);
XCTAssertEqualObjects(result.resumeToken, queryData2.resumeToken);
- XCTAssertEqualObjects(result.snapshotVersion, queryData2.snapshotVersion);
+ XCTAssertEqual(result.snapshotVersion, queryData2.snapshotVersion);
});
}
@@ -278,12 +279,12 @@ NS_ASSUME_NONNULL_BEGIN
[self addMatchingKey:key2 forTargetID:1];
[self addMatchingKey:key3 forTargetID:2];
- FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:1], (@[ key1, key2 ]));
- FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:2], @[ key3 ]);
+ XCTAssertEqual([self.queryCache matchingKeysForTargetID:1], (DocumentKeySet{key1, key2}));
+ XCTAssertEqual([self.queryCache matchingKeysForTargetID:2], (DocumentKeySet{key3}));
[self addMatchingKey:key1 forTargetID:2];
- FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:1], (@[ key1, key2 ]));
- FSTAssertEqualSets([self.queryCache matchingKeysForTargetID:2], (@[ key1, key3 ]));
+ XCTAssertEqual([self.queryCache matchingKeysForTargetID:1], (DocumentKeySet{key1, key2}));
+ XCTAssertEqual([self.queryCache matchingKeysForTargetID:2], (DocumentKeySet{key1, key3}));
});
}
@@ -386,19 +387,18 @@ NS_ASSUME_NONNULL_BEGIN
if ([self isTestBaseClass]) return;
self.persistence.run("testLastRemoteSnapshotVersion", [&]() {
- XCTAssertEqualObjects([self.queryCache lastRemoteSnapshotVersion],
- [FSTSnapshotVersion noVersion]);
+ XCTAssertEqual([self.queryCache lastRemoteSnapshotVersion], SnapshotVersion::None());
// Can set the snapshot version.
- [self.queryCache setLastRemoteSnapshotVersion:FSTTestVersion(42)];
- XCTAssertEqualObjects([self.queryCache lastRemoteSnapshotVersion], FSTTestVersion(42));
+ [self.queryCache setLastRemoteSnapshotVersion:testutil::Version(42)];
+ XCTAssertEqual([self.queryCache lastRemoteSnapshotVersion], testutil::Version(42));
});
// Snapshot version persists restarts.
self.queryCache = [self.persistence queryCache];
self.persistence.run("testLastRemoteSnapshotVersion restart", [&]() {
[self.queryCache start];
- XCTAssertEqualObjects([self.queryCache lastRemoteSnapshotVersion], FSTTestVersion(42));
+ XCTAssertEqual([self.queryCache lastRemoteSnapshotVersion], testutil::Version(42));
});
}
@@ -424,19 +424,17 @@ NS_ASSUME_NONNULL_BEGIN
targetID:targetID
listenSequenceNumber:sequenceNumber
purpose:FSTQueryPurposeListen
- snapshotVersion:FSTTestVersion(version)
+ snapshotVersion:testutil::Version(version)
resumeToken:resumeToken];
}
- (void)addMatchingKey:(const DocumentKey &)key forTargetID:(FSTTargetID)targetID {
- FSTDocumentKeySet *keys = [FSTDocumentKeySet keySet];
- keys = [keys setByAddingObject:key];
+ DocumentKeySet keys{key};
[self.queryCache addMatchingKeys:keys forTargetID:targetID];
}
- (void)removeMatchingKey:(const DocumentKey &)key forTargetID:(FSTTargetID)targetID {
- FSTDocumentKeySet *keys = [FSTDocumentKeySet keySet];
- keys = [keys setByAddingObject:key];
+ DocumentKeySet keys{key};
[self.queryCache removeMatchingKeys:keys forTargetID:targetID];
}
diff --git a/Firestore/Example/Tests/Model/FSTDocumentTests.mm b/Firestore/Example/Tests/Model/FSTDocumentTests.mm
index 24858c5..4e3517c 100644
--- a/Firestore/Example/Tests/Model/FSTDocumentTests.mm
+++ b/Firestore/Example/Tests/Model/FSTDocumentTests.mm
@@ -18,7 +18,6 @@
#import <XCTest/XCTest.h>
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
@@ -28,6 +27,7 @@
namespace testutil = firebase::firestore::testutil;
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::SnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
@@ -38,20 +38,20 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testConstructor {
DocumentKey key = testutil::Key("messages/first");
- FSTSnapshotVersion *version = FSTTestVersion(1);
+ SnapshotVersion version = testutil::Version(1);
FSTObjectValue *data = FSTTestObjectValue(@{ @"a" : @1 });
FSTDocument *doc =
[FSTDocument documentWithData:data key:key version:version hasLocalMutations:NO];
XCTAssertEqualObjects(doc.key, FSTTestDocKey(@"messages/first"));
- XCTAssertEqualObjects(doc.version, version);
+ XCTAssertEqual(doc.version, version);
XCTAssertEqualObjects(doc.data, data);
XCTAssertEqual(doc.hasLocalMutations, NO);
}
- (void)testExtractsFields {
DocumentKey key = testutil::Key("rooms/eros");
- FSTSnapshotVersion *version = FSTTestVersion(1);
+ SnapshotVersion version = testutil::Version(1);
FSTObjectValue *data = FSTTestObjectValue(@{
@"desc" : @"Discuss all the project related stuff",
@"owner" : @{@"name" : @"Jonny", @"title" : @"scallywag"}
diff --git a/Firestore/Example/Tests/Model/FSTMutationTests.mm b/Firestore/Example/Tests/Model/FSTMutationTests.mm
index 936bd38..b9f98ce 100644
--- a/Firestore/Example/Tests/Model/FSTMutationTests.mm
+++ b/Firestore/Example/Tests/Model/FSTMutationTests.mm
@@ -135,7 +135,7 @@ using firebase::firestore::model::TransformOperation;
FSTDocument *expectedDoc = [FSTDocument documentWithData:expectedData
key:FSTTestDocKey(@"collection/key")
- version:FSTTestVersion(0)
+ version:testutil::Version(0)
hasLocalMutations:YES];
XCTAssertEqualObjects(transformedDoc, expectedDoc);
@@ -301,7 +301,7 @@ using firebase::firestore::model::TransformOperation;
FSTDocument *expectedDoc = [FSTDocument documentWithData:FSTTestObjectValue(expectedData)
key:FSTTestDocKey(@"collection/key")
- version:FSTTestVersion(0)
+ version:testutil::Version(0)
hasLocalMutations:YES];
XCTAssertEqualObjects(transformedDoc, expectedDoc);
@@ -315,7 +315,7 @@ using firebase::firestore::model::TransformOperation;
@"collection/key", @{@"foo.bar" : [FIRFieldValue fieldValueForServerTimestamp]});
FSTMutationResult *mutationResult = [[FSTMutationResult alloc]
- initWithVersion:FSTTestVersion(1)
+ initWithVersion:testutil::Version(1)
transformResults:@[ [FSTTimestampValue timestampValue:_timestamp] ]];
FSTMaybeDocument *transformedDoc = [transform applyTo:baseDoc
@@ -340,7 +340,7 @@ using firebase::firestore::model::TransformOperation;
// Server just sends null transform results for array operations.
FSTMutationResult *mutationResult = [[FSTMutationResult alloc]
- initWithVersion:FSTTestVersion(1)
+ initWithVersion:testutil::Version(1)
transformResults:@[ [FSTNullValue nullValue], [FSTNullValue nullValue] ]];
FSTMaybeDocument *transformedDoc = [transform applyTo:baseDoc
@@ -368,7 +368,7 @@ using firebase::firestore::model::TransformOperation;
FSTMutation *set = FSTTestSetMutation(@"collection/key", @{@"foo" : @"new-bar"});
FSTMutationResult *mutationResult =
- [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(4) transformResults:nil];
+ [[FSTMutationResult alloc] initWithVersion:testutil::Version(4) transformResults:nil];
FSTMaybeDocument *setDoc = [set applyTo:baseDoc
baseDocument:baseDoc
localWriteTime:_timestamp
@@ -384,7 +384,7 @@ using firebase::firestore::model::TransformOperation;
FSTMutation *patch = FSTTestPatchMutation("collection/key", @{@"foo" : @"new-bar"}, {});
FSTMutationResult *mutationResult =
- [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(4) transformResults:nil];
+ [[FSTMutationResult alloc] initWithVersion:testutil::Version(4) transformResults:nil];
FSTMaybeDocument *patchedDoc = [patch applyTo:baseDoc
baseDocument:baseDoc
localWriteTime:_timestamp
@@ -394,15 +394,15 @@ using firebase::firestore::model::TransformOperation;
XCTAssertEqualObjects(patchedDoc, FSTTestDoc("collection/key", 0, expectedData, NO));
}
-#define ASSERT_VERSION_TRANSITION(mutation, base, expected) \
- do { \
- FSTMutationResult *mutationResult = \
- [[FSTMutationResult alloc] initWithVersion:FSTTestVersion(0) transformResults:nil]; \
- FSTMaybeDocument *actual = [mutation applyTo:base \
- baseDocument:base \
- localWriteTime:_timestamp \
- mutationResult:mutationResult]; \
- XCTAssertEqualObjects(actual, expected); \
+#define ASSERT_VERSION_TRANSITION(mutation, base, expected) \
+ do { \
+ FSTMutationResult *mutationResult = \
+ [[FSTMutationResult alloc] initWithVersion:testutil::Version(0) transformResults:nil]; \
+ FSTMaybeDocument *actual = [mutation applyTo:base \
+ baseDocument:base \
+ localWriteTime:_timestamp \
+ mutationResult:mutationResult]; \
+ XCTAssertEqualObjects(actual, expected); \
} while (0);
/**
diff --git a/Firestore/core/test/firebase/firestore/model/field_transform_test.cc b/Firestore/Example/Tests/Model/field_transform_test.mm
index b66aeef..a22a0f3 100644
--- a/Firestore/core/test/firebase/firestore/model/field_transform_test.cc
+++ b/Firestore/Example/Tests/Model/field_transform_test.mm
@@ -26,9 +26,8 @@ namespace firestore {
namespace model {
TEST(FieldTransform, Getter) {
- FieldTransform transform(testutil::Field("foo"),
- absl::make_unique<ServerTimestampTransform>(
- ServerTimestampTransform::Get()));
+ FieldTransform transform{testutil::Field("foo"), absl::make_unique<ServerTimestampTransform>(
+ ServerTimestampTransform::Get())};
EXPECT_EQ(testutil::Field("foo"), transform.path());
EXPECT_EQ(ServerTimestampTransform::Get(), transform.transformation());
diff --git a/Firestore/core/test/firebase/firestore/model/transform_operations_test.cc b/Firestore/Example/Tests/Model/transform_operations_test.mm
index ec0882a..247ea13 100644
--- a/Firestore/core/test/firebase/firestore/model/transform_operations_test.cc
+++ b/Firestore/Example/Tests/Model/transform_operations_test.mm
@@ -31,9 +31,25 @@ class DummyOperation : public TransformOperation {
return Type::Test;
}
+ FSTFieldValue* ApplyToLocalView(FSTFieldValue* /* previousValue */,
+ FIRTimestamp* /* localWriteTime */) const override {
+ return nil;
+ }
+
+ FSTFieldValue* ApplyToRemoteDocument(FSTFieldValue* /* previousValue */,
+ FSTFieldValue* /* transformResult */) const override {
+ return nil;
+ }
+
bool operator==(const TransformOperation& other) const override {
return this == &other;
}
+
+ NSUInteger Hash() const override {
+ // arbitrary number, the same as used in ObjC implementation, since all
+ // instances are equal.
+ return 37;
+ }
};
TEST(TransformOperations, ServerTimestamp) {
diff --git a/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm b/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm
index 6ac2a6b..84d0fa1 100644
--- a/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm
+++ b/Firestore/Example/Tests/Remote/FSTRemoteEventTests.mm
@@ -18,6 +18,7 @@
#import <XCTest/XCTest.h>
+#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
@@ -28,7 +29,11 @@
#import "Firestore/Example/Tests/Remote/FSTWatchChange+Testing.h"
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+
+namespace testutil = firebase::firestore::testutil;
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
@@ -55,7 +60,7 @@ NS_ASSUME_NONNULL_BEGIN
listens[targetID] = dummyQueryData;
}
FSTWatchChangeAggregator *aggregator =
- [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:FSTTestVersion(3)
+ [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:testutil::Version(3)
listenTargets:listens
pendingTargetResponses:outstanding];
[aggregator addWatchChanges:watchChanges];
@@ -81,7 +86,7 @@ NS_ASSUME_NONNULL_BEGIN
changes:@[ change1, change2 ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 2);
XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
@@ -143,7 +148,7 @@ NS_ASSUME_NONNULL_BEGIN
outstanding:pendingResponses
changes:@[ change1, change2, change3, change4 ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
// doc1 is ignored because it was part of an inactive target, but doc2 is in the changes
// because it become active.
XCTAssertEqual(event.documentUpdates.size(), 1);
@@ -171,7 +176,7 @@ NS_ASSUME_NONNULL_BEGIN
[self aggregatorWithTargets:@[] outstanding:pendingResponses changes:@[ change1, change2 ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
// doc1 is ignored because it was part of an inactive target
XCTAssertEqual(event.documentUpdates.size(), 0);
@@ -215,7 +220,7 @@ NS_ASSUME_NONNULL_BEGIN
changes:@[ change1, change2, change3, change4, change5 ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 3);
XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
@@ -239,7 +244,7 @@ NS_ASSUME_NONNULL_BEGIN
[self aggregatorWithTargets:@[ @1 ] outstanding:_noPendingResponses changes:@[ change ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 0);
XCTAssertEqual(event.targetChanges.count, 1);
@@ -267,7 +272,7 @@ NS_ASSUME_NONNULL_BEGIN
changes:@[ change1, change2 ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 1);
XCTAssertEqualObjects(event.documentUpdates.at(doc1b.key), doc1b);
@@ -291,7 +296,7 @@ NS_ASSUME_NONNULL_BEGIN
[self aggregatorWithTargets:@[ @1 ] outstanding:_noPendingResponses changes:@[ change ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 0);
XCTAssertEqual(event.targetChanges.count, 1);
FSTTargetChange *targetChange = event.targetChanges[@1];
@@ -333,7 +338,7 @@ NS_ASSUME_NONNULL_BEGIN
changes:@[ change1, change2, change3, change4, change5, change6 ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 2);
XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
@@ -366,7 +371,7 @@ NS_ASSUME_NONNULL_BEGIN
[self aggregatorWithTargets:@[ @1 ] outstanding:_noPendingResponses changes:@[ change ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 0);
XCTAssertEqual(event.targetChanges.count, 1);
XCTAssertEqualObjects(event.targetChanges[@1].mapping, [[FSTUpdateMapping alloc] init]);
@@ -388,7 +393,7 @@ NS_ASSUME_NONNULL_BEGIN
changes:@[ change1, change2, change3 ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 0);
XCTAssertEqual(event.targetChanges.count, 0);
XCTAssertEqual(aggregator.existenceFilters.count, 2);
@@ -420,7 +425,7 @@ NS_ASSUME_NONNULL_BEGIN
changes:@[ change1, change2, change3 ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 2);
XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
@@ -430,7 +435,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTUpdateMapping *mapping1 =
[FSTUpdateMapping mappingWithAddedDocuments:@[ doc1, doc2 ] removedDocuments:@[]];
XCTAssertEqualObjects(event.targetChanges[@1].mapping, mapping1);
- XCTAssertEqualObjects(event.targetChanges[@1].snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.targetChanges[@1].snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.targetChanges[@1].currentStatusUpdate, FSTCurrentStatusUpdateMarkCurrent);
XCTAssertEqualObjects(event.targetChanges[@1].resumeToken, _resumeToken1);
@@ -439,7 +444,7 @@ NS_ASSUME_NONNULL_BEGIN
// Mapping is reset
XCTAssertEqualObjects(event.targetChanges[@1].mapping, [[FSTResetMapping alloc] init]);
// Reset the resume snapshot
- XCTAssertEqualObjects(event.targetChanges[@1].snapshotVersion, FSTTestVersion(0));
+ XCTAssertEqual(event.targetChanges[@1].snapshotVersion, testutil::Version(0));
// Target needs to be set to not current
XCTAssertEqual(event.targetChanges[@1].currentStatusUpdate, FSTCurrentStatusUpdateMarkNotCurrent);
XCTAssertEqual(event.targetChanges[@1].resumeToken.length, 0);
@@ -448,7 +453,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testDocumentUpdate {
FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
FSTDeletedDocument *deletedDoc1 =
- [FSTDeletedDocument documentWithKey:doc1.key version:FSTTestVersion(3)];
+ [FSTDeletedDocument documentWithKey:doc1.key version:testutil::Version(3)];
FSTDocument *doc2 = FSTTestDoc("docs/2", 2, @{ @"value" : @2 }, NO);
FSTDocument *doc3 = FSTTestDoc("docs/3", 3, @{ @"value" : @3 }, NO);
@@ -467,7 +472,7 @@ NS_ASSUME_NONNULL_BEGIN
changes:@[ change1, change2 ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 2);
XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), doc1);
XCTAssertEqualObjects(event.documentUpdates.at(doc2.key), doc2);
@@ -476,7 +481,7 @@ NS_ASSUME_NONNULL_BEGIN
[event addDocumentUpdate:deletedDoc1];
[event addDocumentUpdate:doc3];
- XCTAssertEqualObjects(event.snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.documentUpdates.size(), 3);
// doc1 is replaced
XCTAssertEqualObjects(event.documentUpdates.at(doc1.key), deletedDoc1);
@@ -511,12 +516,12 @@ NS_ASSUME_NONNULL_BEGIN
FSTUpdateMapping *mapping1 =
[FSTUpdateMapping mappingWithAddedDocuments:@[] removedDocuments:@[]];
XCTAssertEqualObjects(event.targetChanges[@1].mapping, mapping1);
- XCTAssertEqualObjects(event.targetChanges[@1].snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.targetChanges[@1].snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.targetChanges[@1].currentStatusUpdate, FSTCurrentStatusUpdateMarkCurrent);
XCTAssertEqualObjects(event.targetChanges[@1].resumeToken, _resumeToken1);
XCTAssertEqualObjects(event.targetChanges[@2].mapping, mapping1);
- XCTAssertEqualObjects(event.targetChanges[@2].snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.targetChanges[@2].snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.targetChanges[@2].currentStatusUpdate, FSTCurrentStatusUpdateMarkCurrent);
XCTAssertEqualObjects(event.targetChanges[@2].resumeToken, resumeToken2);
}
@@ -544,12 +549,12 @@ NS_ASSUME_NONNULL_BEGIN
FSTResetMapping *mapping1 = [FSTResetMapping mappingWithDocuments:@[]];
XCTAssertEqualObjects(event.targetChanges[@1].mapping, mapping1);
- XCTAssertEqualObjects(event.targetChanges[@1].snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.targetChanges[@1].snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.targetChanges[@1].currentStatusUpdate, FSTCurrentStatusUpdateMarkCurrent);
XCTAssertEqualObjects(event.targetChanges[@1].resumeToken, resumeToken2);
XCTAssertEqualObjects(event.targetChanges[@2].mapping, mapping1);
- XCTAssertEqualObjects(event.targetChanges[@2].snapshotVersion, FSTTestVersion(3));
+ XCTAssertEqual(event.targetChanges[@2].snapshotVersion, testutil::Version(3));
XCTAssertEqual(event.targetChanges[@2].currentStatusUpdate, FSTCurrentStatusUpdateNone);
XCTAssertEqualObjects(event.targetChanges[@2].resumeToken, resumeToken3);
}
@@ -557,20 +562,10 @@ NS_ASSUME_NONNULL_BEGIN
- (void)testSynthesizeDeletes {
FSTWatchChange *shouldSynthesize =
[FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:@[ @1 ]];
- FSTWatchChange *wrongState =
- [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange targetIDs:@[ @2 ]];
- FSTWatchChange *hasDocument =
- [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:@[ @3 ]];
- FSTDocument *doc = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
- FSTWatchChange *docChange = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @3 ]
- removedTargetIDs:@[]
- documentKey:doc.key
- document:doc];
- FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @1, @2, @3 ]
- outstanding:_noPendingResponses
- changes:@[ shouldSynthesize, wrongState, hasDocument, docChange ]];
+ FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargets:@[ @1 ]
+ outstanding:_noPendingResponses
+ changes:@[ shouldSynthesize ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
DocumentKey synthesized = DocumentKey::FromPathString("docs/2");
@@ -581,14 +576,41 @@ NS_ASSUME_NONNULL_BEGIN
FSTDeletedDocument *expected =
[FSTDeletedDocument documentWithKey:synthesized version:event.snapshotVersion];
XCTAssertEqualObjects(expected, event.documentUpdates.at(synthesized));
+ XCTAssertTrue(event.limboDocumentChanges.contains(synthesized));
+}
+
+- (void)testDoesntSynthesizeDeletesForWrongState {
+ FSTWatchChange *wrongState =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateNoChange targetIDs:@[ @2 ]];
- DocumentKey notSynthesized1 = DocumentKey::FromPathString("docs/no1");
- [event synthesizeDeleteForLimboTargetChange:event.targetChanges[@2] key:notSynthesized1];
- XCTAssertEqual(event.documentUpdates.find(notSynthesized1), event.documentUpdates.end());
+ FSTWatchChangeAggregator *aggregator =
+ [self aggregatorWithTargets:@[ @2 ] outstanding:_noPendingResponses changes:@[ wrongState ]];
+ FSTRemoteEvent *event = [aggregator remoteEvent];
+
+ DocumentKey notSynthesized = DocumentKey::FromPathString("docs/no1");
+ [event synthesizeDeleteForLimboTargetChange:event.targetChanges[@2] key:notSynthesized];
+ XCTAssertEqual(event.documentUpdates.find(notSynthesized), event.documentUpdates.end());
+ XCTAssertFalse(event.limboDocumentChanges.contains(notSynthesized));
+}
+
+- (void)testDoesntSynthesizeDeletesForExistingDoc {
+ FSTWatchChange *hasDocument =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:@[ @3 ]];
+ FSTDocument *doc = FSTTestDoc("docs/1", 1, @{ @"value" : @1 }, NO);
+ FSTWatchChange *docChange = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @3 ]
+ removedTargetIDs:@[]
+ documentKey:doc.key
+ document:doc];
+ FSTWatchChangeAggregator *aggregator = [self aggregatorWithTargets:@[ @3 ]
+ outstanding:_noPendingResponses
+ changes:@[ hasDocument, docChange ]];
+
+ FSTRemoteEvent *event = [aggregator remoteEvent];
[event synthesizeDeleteForLimboTargetChange:event.targetChanges[@3] key:doc.key];
FSTMaybeDocument *docData = event.documentUpdates.at(doc.key);
XCTAssertFalse([docData isKindOfClass:[FSTDeletedDocument class]]);
+ XCTAssertFalse(event.limboDocumentChanges.contains(doc.key));
}
- (void)testFilterUpdates {
@@ -599,44 +621,108 @@ NS_ASSUME_NONNULL_BEGIN
documentKey:newDoc.key
document:newDoc];
- FSTWatchTargetChange *resetTargetChange =
- [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateReset
- targetIDs:@[ @2 ]
- resumeToken:_resumeToken1];
-
FSTWatchChange *existingDocChange =
- [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @2 ]
+ [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
removedTargetIDs:@[]
documentKey:existingDoc.key
document:existingDoc];
FSTWatchChangeAggregator *aggregator =
- [self aggregatorWithTargets:@[ @1, @2 ]
+ [self aggregatorWithTargets:@[ @1 ]
outstanding:_noPendingResponses
- changes:@[ newDocChange, resetTargetChange, existingDocChange ]];
+ changes:@[ newDocChange, existingDocChange ]];
FSTRemoteEvent *event = [aggregator remoteEvent];
- FSTDocumentKeySet *existingKeys = [[FSTDocumentKeySet keySet] setByAddingObject:existingDoc.key];
+ DocumentKeySet existingKeys = DocumentKeySet{existingDoc.key};
FSTTargetChange *updateChange = event.targetChanges[@1];
XCTAssertTrue([updateChange.mapping isKindOfClass:[FSTUpdateMapping class]]);
FSTUpdateMapping *update = (FSTUpdateMapping *)updateChange.mapping;
FSTDocumentKey *existingDocKey = existingDoc.key;
FSTDocumentKey *newDocKey = newDoc.key;
- XCTAssertTrue([update.addedDocuments containsObject:existingDocKey]);
+ XCTAssertTrue(update.addedDocuments.contains(existingDocKey));
- [event filterUpdatesFromTargetChange:updateChange existingDocuments:existingKeys];
+ [update filterUpdatesUsingExistingKeys:existingKeys];
// Now it's been filtered, since it already existed.
- XCTAssertFalse([update.addedDocuments containsObject:existingDocKey]);
- XCTAssertTrue([update.addedDocuments containsObject:newDocKey]);
+ XCTAssertFalse(update.addedDocuments.contains(existingDocKey));
+ XCTAssertTrue(update.addedDocuments.contains(newDocKey));
+}
+
+- (void)testDoesntFilterResets {
+ FSTDocument *existingDoc = FSTTestDoc("docs/existing", 1, @{@"some" : @"data"}, NO);
+ const DocumentKey &existingDocKey = existingDoc.key;
+ FSTWatchTargetChange *resetTargetChange =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateReset
+ targetIDs:@[ @2 ]
+ resumeToken:_resumeToken1];
+ FSTWatchChange *existingDocChange =
+ [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @2 ]
+ removedTargetIDs:@[]
+ documentKey:existingDocKey
+ document:existingDoc];
+ FSTWatchChangeAggregator *aggregator =
+ [self aggregatorWithTargets:@[ @2 ]
+ outstanding:_noPendingResponses
+ changes:@[ resetTargetChange, existingDocChange ]];
+ FSTRemoteEvent *event = [aggregator remoteEvent];
+ DocumentKeySet existingKeys = DocumentKeySet{existingDocKey};
FSTTargetChange *resetChange = event.targetChanges[@2];
XCTAssertTrue([resetChange.mapping isKindOfClass:[FSTResetMapping class]]);
FSTResetMapping *resetMapping = (FSTResetMapping *)resetChange.mapping;
- XCTAssertTrue([resetMapping.documents containsObject:existingDocKey]);
+ XCTAssertTrue(resetMapping.documents.contains(existingDocKey));
- [event filterUpdatesFromTargetChange:resetChange existingDocuments:existingKeys];
+ [resetMapping filterUpdatesUsingExistingKeys:existingKeys];
// Document is still there, even though it already exists. Reset mappings don't get filtered.
- XCTAssertTrue([resetMapping.documents containsObject:existingDocKey]);
+ XCTAssertTrue(resetMapping.documents.contains(existingDocKey));
+}
+
+- (void)testTracksLimboDocuments {
+ // Add 3 docs: 1 is limbo and non-limbo, 2 is limbo-only, 3 is non-limbo
+ FSTDocument *doc1 = FSTTestDoc("docs/1", 1, @{@"key" : @"value"}, NO);
+ FSTDocument *doc2 = FSTTestDoc("docs/2", 1, @{@"key" : @"value"}, NO);
+ FSTDocument *doc3 = FSTTestDoc("docs/3", 1, @{@"key" : @"value"}, NO);
+
+ // Target 2 is a limbo target
+
+ FSTWatchChange *docChange1 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1, @2 ]
+ removedTargetIDs:@[]
+ documentKey:doc1.key
+ document:doc1];
+
+ FSTWatchChange *docChange2 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @2 ]
+ removedTargetIDs:@[]
+ documentKey:doc2.key
+ document:doc2];
+
+ FSTWatchChange *docChange3 = [[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:@[ @1 ]
+ removedTargetIDs:@[]
+ documentKey:doc3.key
+ document:doc3];
+
+ FSTWatchChange *targetsChange =
+ [FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent targetIDs:@[ @1, @2 ]];
+
+ NSMutableDictionary<NSNumber *, FSTQueryData *> *listens = [NSMutableDictionary dictionary];
+ listens[@1] = [FSTQueryData alloc];
+ listens[@2] = [[FSTQueryData alloc] initWithQuery:[FSTQuery alloc]
+ targetID:2
+ listenSequenceNumber:1000
+ purpose:FSTQueryPurposeLimboResolution];
+ FSTWatchChangeAggregator *aggregator =
+ [[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:testutil::Version(3)
+ listenTargets:listens
+ pendingTargetResponses:@{}];
+
+ [aggregator addWatchChanges:@[ docChange1, docChange2, docChange3, targetsChange ]];
+
+ FSTRemoteEvent *event = [aggregator remoteEvent];
+ DocumentKeySet limboDocChanges = event.limboDocumentChanges;
+ // Doc1 is in both limbo and non-limbo targets, therefore not tracked as limbo
+ XCTAssertFalse(limboDocChanges.contains(doc1.key));
+ // Doc2 is only in the limbo target, so is tracked as a limbo document
+ XCTAssertTrue(limboDocChanges.contains(doc2.key));
+ // Doc3 is only in the non-limbo target, therefore not tracked as limbo
+ XCTAssertFalse(limboDocChanges.contains(doc3.key));
}
@end
diff --git a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm
index bbb3822..da47aaa 100644
--- a/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm
+++ b/Firestore/Example/Tests/Remote/FSTSerializerBetaTests.mm
@@ -37,7 +37,6 @@
#import "Firestore/Protos/objc/google/type/Latlng.pbobjc.h"
#import "Firestore/Source/API/FIRFieldValue+Internal.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTDocumentKey.h"
@@ -58,10 +57,12 @@
namespace testutil = firebase::firestore::testutil;
namespace util = firebase::firestore::util;
+using firebase::Timestamp;
using firebase::firestore::model::DatabaseId;
using firebase::firestore::model::FieldMask;
using firebase::firestore::model::FieldTransform;
using firebase::firestore::model::Precondition;
+using firebase::firestore::model::SnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
@@ -424,8 +425,7 @@ NS_ASSUME_NONNULL_BEGIN
precondition:Precondition::UpdateTime(testutil::Version(4))];
GCFSWrite *proto = [GCFSWrite message];
proto.update = [self.serializer encodedDocumentWithFields:mutation.value key:mutation.key];
- proto.currentDocument.updateTime =
- [self.serializer encodedTimestamp:[[FIRTimestamp alloc] initWithSeconds:0 nanoseconds:4000]];
+ proto.currentDocument.updateTime = [self.serializer encodedTimestamp:Timestamp{0, 4000}];
[self assertRoundTripForMutation:mutation proto:proto];
}
@@ -708,7 +708,7 @@ NS_ASSUME_NONNULL_BEGIN
targetID:1
listenSequenceNumber:0
purpose:FSTQueryPurposeListen
- snapshotVersion:[FSTSnapshotVersion noVersion]
+ snapshotVersion:SnapshotVersion::None()
resumeToken:FSTTestData(1, 2, 3, -1)];
GCFSTarget *expected = [GCFSTarget message];
@@ -729,7 +729,7 @@ NS_ASSUME_NONNULL_BEGIN
targetID:1
listenSequenceNumber:0
purpose:FSTQueryPurposeListen
- snapshotVersion:[FSTSnapshotVersion noVersion]
+ snapshotVersion:SnapshotVersion::None()
resumeToken:[NSData data]];
}
diff --git a/Firestore/Example/Tests/SpecTests/FSTMockDatastore.h b/Firestore/Example/Tests/SpecTests/FSTMockDatastore.h
index e1ea2fb..6951f9c 100644
--- a/Firestore/Example/Tests/SpecTests/FSTMockDatastore.h
+++ b/Firestore/Example/Tests/SpecTests/FSTMockDatastore.h
@@ -18,6 +18,8 @@
#import "Firestore/Source/Remote/FSTDatastore.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTMockDatastore : FSTDatastore
@@ -41,11 +43,13 @@ NS_ASSUME_NONNULL_BEGIN
/** Injects an Added WatchChange that marks the given targetIDs current. */
- (void)writeWatchCurrentWithTargetIDs:(NSArray<FSTBoxedTargetID *> *)targetIDs
- snapshotVersion:(FSTSnapshotVersion *)snapshotVersion
+ snapshotVersion:
+ (const firebase::firestore::model::SnapshotVersion &)snapshotVersion
resumeToken:(NSData *)resumeToken;
/** Injects a WatchChange as though it had come from the backend. */
-- (void)writeWatchChange:(FSTWatchChange *)change snapshotVersion:(FSTSnapshotVersion *)snap;
+- (void)writeWatchChange:(FSTWatchChange *)change
+ snapshotVersion:(const firebase::firestore::model::SnapshotVersion &)snap;
/** Injects a stream failure as though it had come from the backend. */
- (void)failWatchStreamWithError:(NSError *)error;
@@ -67,7 +71,7 @@ NS_ASSUME_NONNULL_BEGIN
- (int)writesSent;
/** Injects a write ack as though it had come from the backend in response to a write. */
-- (void)ackWriteWithVersion:(FSTSnapshotVersion *)commitVersion
+- (void)ackWriteWithVersion:(const firebase::firestore::model::SnapshotVersion &)commitVersion
mutationResults:(NSArray<FSTMutationResult *> *)results;
/** Injects a stream failure as though it had come from the backend. */
diff --git a/Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm b/Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm
index 6715b24..dd34556 100644
--- a/Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm
+++ b/Firestore/Example/Tests/SpecTests/FSTMockDatastore.mm
@@ -16,7 +16,6 @@
#import "Firestore/Example/Tests/SpecTests/FSTMockDatastore.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
@@ -36,6 +35,7 @@ using firebase::firestore::auth::CredentialsProvider;
using firebase::firestore::auth::EmptyCredentialsProvider;
using firebase::firestore::core::DatabaseInfo;
using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::SnapshotVersion;
@class GRPCProtoCall;
@@ -120,9 +120,9 @@ NS_ASSUME_NONNULL_BEGIN
FSTLog(@"watchQuery: %d: %@", query.targetID, query.query);
self.datastore.watchStreamRequestCount += 1;
// Snapshot version is ignored on the wire
- FSTQueryData *sentQueryData =
- [query queryDataByReplacingSnapshotVersion:[FSTSnapshotVersion noVersion]
- resumeToken:query.resumeToken];
+ FSTQueryData *sentQueryData = [query queryDataByReplacingSnapshotVersion:SnapshotVersion::None()
+ resumeToken:query.resumeToken
+ sequenceNumber:query.sequenceNumber];
self.activeTargets[@(query.targetID)] = sentQueryData;
}
@@ -138,7 +138,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Helper methods.
-- (void)writeWatchChange:(FSTWatchChange *)change snapshotVersion:(FSTSnapshotVersion *)snap {
+- (void)writeWatchChange:(FSTWatchChange *)change snapshotVersion:(const SnapshotVersion &)snap {
if ([change isKindOfClass:[FSTWatchTargetChange class]]) {
FSTWatchTargetChange *targetChange = (FSTWatchTargetChange *)change;
if (targetChange.cause) {
@@ -242,7 +242,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Helper methods.
/** Injects a write ack as though it had come from the backend in response to a write. */
-- (void)ackWriteWithVersion:(FSTSnapshotVersion *)commitVersion
+- (void)ackWriteWithVersion:(const SnapshotVersion &)commitVersion
mutationResults:(NSArray<FSTMutationResult *> *)results {
[self.delegate writeStreamDidReceiveResponseWithVersion:commitVersion mutationResults:results];
}
@@ -326,7 +326,7 @@ NS_ASSUME_NONNULL_BEGIN
return [self.writeStream sentMutationsCount];
}
-- (void)ackWriteWithVersion:(FSTSnapshotVersion *)commitVersion
+- (void)ackWriteWithVersion:(const SnapshotVersion &)commitVersion
mutationResults:(NSArray<FSTMutationResult *> *)results {
[self.writeStream ackWriteWithVersion:commitVersion mutationResults:results];
}
@@ -340,11 +340,11 @@ NS_ASSUME_NONNULL_BEGIN
[FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateAdded
targetIDs:targetIDs
cause:nil];
- [self writeWatchChange:change snapshotVersion:[FSTSnapshotVersion noVersion]];
+ [self writeWatchChange:change snapshotVersion:SnapshotVersion::None()];
}
- (void)writeWatchCurrentWithTargetIDs:(NSArray<FSTBoxedTargetID *> *)targetIDs
- snapshotVersion:(FSTSnapshotVersion *)snapshotVersion
+ snapshotVersion:(const SnapshotVersion &)snapshotVersion
resumeToken:(NSData *)resumeToken {
FSTWatchTargetChange *change =
[FSTWatchTargetChange changeWithState:FSTWatchTargetChangeStateCurrent
@@ -353,7 +353,7 @@ NS_ASSUME_NONNULL_BEGIN
[self writeWatchChange:change snapshotVersion:snapshotVersion];
}
-- (void)writeWatchChange:(FSTWatchChange *)change snapshotVersion:(FSTSnapshotVersion *)snap {
+- (void)writeWatchChange:(FSTWatchChange *)change snapshotVersion:(const SnapshotVersion &)snap {
[self.watchStream writeWatchChange:change snapshotVersion:snap];
}
diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
index 128f825..5a7cb72 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
+++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm
@@ -24,7 +24,6 @@
#import "Firestore/Source/Core/FSTEventManager.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Local/FSTEagerGarbageCollector.h"
#import "Firestore/Source/Local/FSTNoOpGarbageCollector.h"
#import "Firestore/Source/Local/FSTPersistence.h"
@@ -46,11 +45,15 @@
#include "Firestore/core/src/firebase/firestore/auth/user.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
+namespace testutil = firebase::firestore::testutil;
namespace util = firebase::firestore::util;
using firebase::firestore::auth::User;
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::SnapshotVersion;
using firebase::firestore::model::TargetId;
NS_ASSUME_NONNULL_BEGIN
@@ -145,8 +148,8 @@ static NSString *const kNoIOSTag = @"no-ios";
}
}
-- (FSTSnapshotVersion *)parseVersion:(NSNumber *_Nullable)version {
- return FSTTestVersion(version.longLongValue);
+- (SnapshotVersion)parseVersion:(NSNumber *_Nullable)version {
+ return testutil::Version(version.longLongValue);
}
- (FSTDocumentViewChange *)parseChange:(NSArray *)change ofType:(FSTDocumentViewChangeType)type {
@@ -254,9 +257,11 @@ static NSString *const kNoIOSTag = @"no-ios";
NSArray *docSpec = watchEntity[@"doc"];
FSTDocumentKey *key = FSTTestDocKey(docSpec[0]);
FSTObjectValue *value = FSTTestObjectValue(docSpec[2]);
- FSTSnapshotVersion *version = [self parseVersion:docSpec[1]];
- FSTMaybeDocument *doc =
- [FSTDocument documentWithData:value key:key version:version hasLocalMutations:NO];
+ SnapshotVersion version = [self parseVersion:docSpec[1]];
+ FSTMaybeDocument *doc = [FSTDocument documentWithData:value
+ key:key
+ version:std::move(version)
+ hasLocalMutations:NO];
FSTWatchChange *change =
[[FSTDocumentWatchChange alloc] initWithUpdatedTargetIDs:watchEntity[@"targets"]
removedTargetIDs:watchEntity[@"removedTargets"]
@@ -309,7 +314,7 @@ static NSString *const kNoIOSTag = @"no-ios";
}
- (void)doWriteAck:(NSDictionary *)spec {
- FSTSnapshotVersion *version = [self parseVersion:spec[@"version"]];
+ SnapshotVersion version = [self parseVersion:spec[@"version"]];
NSNumber *expectUserCallback = spec[@"expectUserCallback"];
FSTMutationResult *mutationResult =
@@ -550,7 +555,7 @@ static NSString *const kNoIOSTag = @"no-ios";
targetID:targetID
listenSequenceNumber:0
purpose:FSTQueryPurposeListen
- snapshotVersion:[FSTSnapshotVersion noVersion]
+ snapshotVersion:SnapshotVersion::None()
resumeToken:resumeToken];
}];
self.driver.expectedActiveTargets = expectedActiveTargets;
@@ -598,10 +603,13 @@ static NSString *const kNoIOSTag = @"no-ios";
// XCTAssertEqualObjects(actualTargets[targetID], queryData);
FSTQueryData *actual = actualTargets[targetID];
- XCTAssertEqualObjects(actual.query, queryData.query);
- XCTAssertEqual(actual.targetID, queryData.targetID);
- XCTAssertEqualObjects(actual.snapshotVersion, queryData.snapshotVersion);
- XCTAssertEqualObjects(actual.resumeToken, queryData.resumeToken);
+ XCTAssertNotNil(actual);
+ if (actual) {
+ XCTAssertEqualObjects(actual.query, queryData.query);
+ XCTAssertEqual(actual.targetID, queryData.targetID);
+ XCTAssertEqual(actual.snapshotVersion, queryData.snapshotVersion);
+ XCTAssertEqualObjects(actual.resumeToken, queryData.resumeToken);
+ }
[actualTargets removeObjectForKey:targetID];
}];
diff --git a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
index ac44cb5..cfd8974 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
+++ b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h
@@ -25,13 +25,13 @@
#include "Firestore/core/src/firebase/firestore/auth/user.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
@class FSTDocumentKey;
@class FSTMutation;
@class FSTMutationResult;
@class FSTQuery;
@class FSTQueryData;
-@class FSTSnapshotVersion;
@class FSTViewSnapshot;
@class FSTWatchChange;
@protocol FSTGarbageCollector;
@@ -150,7 +150,7 @@ typedef std::unordered_map<firebase::firestore::auth::User,
* simulating the server having sent a complete snapshot.
*/
- (void)receiveWatchChange:(FSTWatchChange *)change
- snapshotVersion:(FSTSnapshotVersion *_Nullable)snapshot;
+ snapshotVersion:(const firebase::firestore::model::SnapshotVersion &)snapshot;
/**
* Delivers a watch stream error as if the Streaming Watch backend has generated some kind of error.
@@ -195,7 +195,8 @@ typedef std::unordered_map<firebase::firestore::auth::User,
* the mutation. Snapshot versions must be monotonically increasing.
* @param mutationResults The mutation results for the write that is being acked.
*/
-- (FSTOutstandingWrite *)receiveWriteAckWithVersion:(FSTSnapshotVersion *)commitVersion
+- (FSTOutstandingWrite *)receiveWriteAckWithVersion:
+ (const firebase::firestore::model::SnapshotVersion &)commitVersion
mutationResults:(NSArray<FSTMutationResult *> *)mutationResults;
/**
diff --git a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm
index f167ce5..2aa0e30 100644
--- a/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm
+++ b/Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.mm
@@ -24,7 +24,6 @@
#import "Firestore/Source/Core/FSTEventManager.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Core/FSTSyncEngine.h"
#import "Firestore/Source/Local/FSTLocalStore.h"
#import "Firestore/Source/Local/FSTPersistence.h"
@@ -49,6 +48,7 @@ using firebase::firestore::auth::User;
using firebase::firestore::core::DatabaseInfo;
using firebase::firestore::model::DatabaseId;
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::SnapshotVersion;
using firebase::firestore::model::TargetId;
NS_ASSUME_NONNULL_BEGIN
@@ -243,7 +243,7 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
-- (FSTOutstandingWrite *)receiveWriteAckWithVersion:(FSTSnapshotVersion *)commitVersion
+- (FSTOutstandingWrite *)receiveWriteAckWithVersion:(const SnapshotVersion &)commitVersion
mutationResults:
(NSArray<FSTMutationResult *> *)mutationResults {
FSTOutstandingWrite *write = [self currentOutstandingWrites].firstObject;
@@ -333,7 +333,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)receiveWatchChange:(FSTWatchChange *)change
- snapshotVersion:(FSTSnapshotVersion *_Nullable)snapshot {
+ snapshotVersion:(const SnapshotVersion &)snapshot {
[self.dispatchQueue dispatchSync:^{
[self.datastore writeWatchChange:change snapshotVersion:snapshot];
}];
diff --git a/Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm b/Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm
index 811fa34..1f49aa4 100644
--- a/Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm
+++ b/Firestore/Example/Tests/Util/FSTDispatchQueueTests.mm
@@ -66,9 +66,10 @@ static const FSTTimerID timerID3 = FSTTimerIDWriteStreamConnectionBackoff;
XCTAssertNotNil(caught);
XCTAssertEqualObjects(caught.name, NSInternalInconsistencyException);
- XCTAssertTrue(
- [caught.reason hasPrefix:@"FIRESTORE INTERNAL ASSERTION FAILED: "
- @"dispatchAsync called when we are already running on target"]);
+ XCTAssertTrue([caught.reason
+ hasPrefix:
+ @"FIRESTORE INTERNAL ASSERTION FAILED: "
+ @"Enqueue methods cannot be called when we are already running on target executor"]);
}
- (void)testDispatchAsyncAllowingSameQueueActuallyAllowsSameQueue {
@@ -133,9 +134,10 @@ static const FSTTimerID timerID3 = FSTTimerIDWriteStreamConnectionBackoff;
XCTAssertNotNil(caught);
XCTAssertEqualObjects(caught.name, NSInternalInconsistencyException);
- XCTAssertTrue(
- [caught.reason hasPrefix:@"FIRESTORE INTERNAL ASSERTION FAILED: "
- @"dispatchSync called when we are already running on target"]);
+ XCTAssertTrue([caught.reason
+ hasPrefix:
+ @"FIRESTORE INTERNAL ASSERTION FAILED: "
+ @"Enqueue methods cannot be called when we are already running on target executor"]);
}
- (void)testVerifyIsCurrentQueueActuallyRequiresCurrentQueue {
@@ -150,7 +152,8 @@ static const FSTTimerID timerID3 = FSTTimerIDWriteStreamConnectionBackoff;
}
XCTAssertNotNil(caught);
XCTAssertTrue([caught.reason hasPrefix:@"FIRESTORE INTERNAL ASSERTION FAILED: "
- @"We are running on the wrong dispatch queue"]);
+ @"Expected to be called by the executor "
+ @"associated with this queue"]);
}
- (void)testVerifyIsCurrentQueueRequiresOperationIsInProgress {
@@ -165,7 +168,7 @@ static const FSTTimerID timerID3 = FSTTimerIDWriteStreamConnectionBackoff;
XCTAssertNotNil(caught);
XCTAssertTrue(
[caught.reason hasPrefix:@"FIRESTORE INTERNAL ASSERTION FAILED: "
- @"verifyIsCurrentQueue called outside enterCheckedOperation"]);
+ @"VerifyIsCurrentQueue called when no operation is executing"]);
}
- (void)testVerifyIsCurrentQueueWorksWithOperationIsInProgress {
@@ -194,9 +197,10 @@ static const FSTTimerID timerID3 = FSTTimerIDWriteStreamConnectionBackoff;
}];
XCTAssertNil(problem);
XCTAssertNotNil(caught);
- XCTAssertTrue([caught.reason
- hasPrefix:@"FIRESTORE INTERNAL ASSERTION FAILED: "
- @"enterCheckedOperation may not be called when an operation is in progress"]);
+ XCTAssertTrue(
+ [caught.reason hasPrefix:@"FIRESTORE INTERNAL ASSERTION FAILED: "
+ @"ExecuteBlocking may not be called before the previous operation "
+ @"finishes executing"]);
}
/**
@@ -217,8 +221,10 @@ static const FSTTimerID timerID3 = FSTTimerIDWriteStreamConnectionBackoff;
_expectation = [self expectationWithDescription:@"Expected steps"];
_expectedSteps = @[ @1, @2, @3, @4 ];
[_queue dispatchAsync:[self blockForStep:1]];
- [_queue dispatchAfterDelay:0.005 timerID:timerID1 block:[self blockForStep:4]];
- [_queue dispatchAfterDelay:0.001 timerID:timerID2 block:[self blockForStep:3]];
+ [_queue dispatchAsync:^{
+ [_queue dispatchAfterDelay:0.005 timerID:timerID1 block:[self blockForStep:4]];
+ [_queue dispatchAfterDelay:0.001 timerID:timerID2 block:[self blockForStep:3]];
+ }];
[_queue dispatchAsync:[self blockForStep:2]];
[self awaitExpectations];
@@ -244,8 +250,10 @@ static const FSTTimerID timerID3 = FSTTimerIDWriteStreamConnectionBackoff;
- (void)testCanManuallyDrainAllDelayedCallbacksForTesting {
[_queue dispatchAsync:[self blockForStep:1]];
- [_queue dispatchAfterDelay:20 timerID:timerID1 block:[self blockForStep:4]];
- [_queue dispatchAfterDelay:10 timerID:timerID2 block:[self blockForStep:3]];
+ [_queue dispatchAsync:^{
+ [_queue dispatchAfterDelay:20 timerID:timerID1 block:[self blockForStep:4]];
+ [_queue dispatchAfterDelay:10 timerID:timerID2 block:[self blockForStep:3]];
+ }];
[_queue dispatchAsync:[self blockForStep:2]];
[_queue runDelayedCallbacksUntil:FSTTimerIDAll];
@@ -254,9 +262,11 @@ static const FSTTimerID timerID3 = FSTTimerIDWriteStreamConnectionBackoff;
- (void)testCanManuallyDrainSpecificDelayedCallbacksForTesting {
[_queue dispatchAsync:[self blockForStep:1]];
- [_queue dispatchAfterDelay:20 timerID:timerID1 block:[self blockForStep:5]];
- [_queue dispatchAfterDelay:10 timerID:timerID2 block:[self blockForStep:3]];
- [_queue dispatchAfterDelay:15 timerID:timerID3 block:[self blockForStep:4]];
+ [_queue dispatchAsync:^{
+ [_queue dispatchAfterDelay:20 timerID:timerID1 block:[self blockForStep:5]];
+ [_queue dispatchAfterDelay:10 timerID:timerID2 block:[self blockForStep:3]];
+ [_queue dispatchAfterDelay:15 timerID:timerID3 block:[self blockForStep:4]];
+ }];
[_queue dispatchAsync:[self blockForStep:2]];
[_queue runDelayedCallbacksUntil:timerID3];
diff --git a/Firestore/Example/Tests/Util/FSTEventAccumulator.h b/Firestore/Example/Tests/Util/FSTEventAccumulator.h
index baa501b..58b802b 100644
--- a/Firestore/Example/Tests/Util/FSTEventAccumulator.h
+++ b/Firestore/Example/Tests/Util/FSTEventAccumulator.h
@@ -25,15 +25,21 @@ NS_ASSUME_NONNULL_BEGIN
typedef void (^FSTValueEventHandler)(id _Nullable, NSError *_Nullable error);
-@interface FSTEventAccumulator : NSObject
+@interface FSTEventAccumulator <EventType> : NSObject
+ (instancetype)accumulatorForTest:(XCTestCase *)testCase;
- (instancetype)init NS_UNAVAILABLE;
-- (id)awaitEventWithName:(NSString *)name;
+- (EventType)awaitEventWithName:(NSString *)name;
-- (NSArray<id> *)awaitEvents:(NSUInteger)events name:(NSString *)name;
+- (NSArray<EventType> *)awaitEvents:(NSUInteger)events name:(NSString *)name;
+
+/** Waits for a latency compensated local snapshot. */
+- (EventType)awaitLocalEvent;
+
+/** Waits for a snapshot that has no pending writes */
+- (EventType)awaitRemoteEvent;
@property(nonatomic, strong, readonly) FSTValueEventHandler valueEventHandler;
diff --git a/Firestore/Example/Tests/Util/FSTEventAccumulator.mm b/Firestore/Example/Tests/Util/FSTEventAccumulator.mm
index 623ba2d..3ab6035 100644
--- a/Firestore/Example/Tests/Util/FSTEventAccumulator.mm
+++ b/Firestore/Example/Tests/Util/FSTEventAccumulator.mm
@@ -18,6 +18,9 @@
#import <XCTest/XCTest.h>
+#import "Firestore/Source/Public/FIRDocumentSnapshot.h"
+#import "Firestore/Source/Public/FIRQuerySnapshot.h"
+#import "Firestore/Source/Public/FIRSnapshotMetadata.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Example/Tests/Util/XCTestCase+Await.h"
@@ -68,6 +71,31 @@ NS_ASSUME_NONNULL_BEGIN
return events[0];
}
+- (id)awaitLocalEvent {
+ id event;
+ do {
+ event = [self awaitEventWithName:@"Local Event"];
+ } while (![self hasPendingWrites:event]);
+ return event;
+}
+
+- (id)awaitRemoteEvent {
+ id event;
+ do {
+ event = [self awaitEventWithName:@"Remote Event"];
+ } while ([self hasPendingWrites:event]);
+ return event;
+}
+
+- (BOOL)hasPendingWrites:(id)event {
+ if ([event isKindOfClass:[FIRDocumentSnapshot class]]) {
+ return ((FIRDocumentSnapshot *)event).metadata.hasPendingWrites;
+ } else {
+ FSTAssert([event isKindOfClass:[FIRQuerySnapshot class]], @"Unexpected event: %@", event);
+ return ((FIRQuerySnapshot *)event).metadata.hasPendingWrites;
+ }
+}
+
- (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
diff --git a/Firestore/Example/Tests/Util/FSTHelpers.h b/Firestore/Example/Tests/Util/FSTHelpers.h
index 131da2d..ccc01ca 100644
--- a/Firestore/Example/Tests/Util/FSTHelpers.h
+++ b/Firestore/Example/Tests/Util/FSTHelpers.h
@@ -21,7 +21,6 @@
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
-#import "Firestore/Source/Model/FSTDocumentKeySet.h"
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
@@ -40,7 +39,6 @@
@class FSTQuery;
@class FSTRemoteEvent;
@class FSTSetMutation;
-@class FSTSnapshotVersion;
@class FSTSortOrder;
@class FSTTargetChange;
@class FIRTimestamp;
@@ -182,15 +180,9 @@ FSTObjectValue *FSTTestObjectValue(NSDictionary<NSString *, id> *data);
/** A convenience method for creating document keys for tests. */
FSTDocumentKey *FSTTestDocKey(NSString *path);
-/** A convenience method for creating a document key set for tests. */
-FSTDocumentKeySet *FSTTestDocKeySet(NSArray<FSTDocumentKey *> *keys);
-
/** Allow tests to just use an int literal for versions. */
typedef int64_t FSTTestSnapshotVersion;
-/** A convenience method for creating snapshot versions for tests. */
-FSTSnapshotVersion *FSTTestVersion(FSTTestSnapshotVersion version);
-
/** A convenience method for creating docs for tests. */
FSTDocument *FSTTestDoc(const absl::string_view path,
FSTTestSnapshotVersion version,
diff --git a/Firestore/Example/Tests/Util/FSTHelpers.mm b/Firestore/Example/Tests/Util/FSTHelpers.mm
index 888a45f..dbd19aa 100644
--- a/Firestore/Example/Tests/Util/FSTHelpers.mm
+++ b/Firestore/Example/Tests/Util/FSTHelpers.mm
@@ -29,7 +29,6 @@
#import "Firestore/Source/API/FIRFieldPath+Internal.h"
#import "Firestore/Source/API/FSTUserDataConverter.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Core/FSTView.h"
#import "Firestore/Source/Core/FSTViewSnapshot.h"
#import "Firestore/Source/Local/FSTLocalViewChanges.h"
@@ -45,6 +44,7 @@
#include "Firestore/core/src/firebase/firestore/model/database_id.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
#include "Firestore/core/src/firebase/firestore/model/field_mask.h"
#include "Firestore/core/src/firebase/firestore/model/field_transform.h"
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
@@ -67,15 +67,13 @@ using firebase::firestore::model::Precondition;
using firebase::firestore::model::ResourcePath;
using firebase::firestore::model::ServerTimestampTransform;
using firebase::firestore::model::TransformOperation;
+using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
/** A string sentinel that can be used with FSTTestPatchMutation() to mark a field for deletion. */
static NSString *const kDeleteSentinel = @"<DELETE>";
-static const int kMicrosPerSec = 1000000;
-static const int kMillisPerSec = 1000;
-
FIRTimestamp *FSTTestTimestamp(int year, int month, int day, int hour, int minute, int second) {
NSDate *date = FSTTestDate(year, month, day, hour, minute, second);
return [FIRTimestamp timestampWithDate:date];
@@ -150,22 +148,6 @@ FSTDocumentKey *FSTTestDocKey(NSString *path) {
return [FSTDocumentKey keyWithPathString:path];
}
-FSTDocumentKeySet *FSTTestDocKeySet(NSArray<FSTDocumentKey *> *keys) {
- FSTDocumentKeySet *result = [FSTDocumentKeySet keySet];
- for (FSTDocumentKey *key in keys) {
- result = [result setByAddingObject:key];
- }
- return result;
-}
-
-FSTSnapshotVersion *FSTTestVersion(FSTTestSnapshotVersion versionMicroseconds) {
- int64_t seconds = versionMicroseconds / kMicrosPerSec;
- int32_t nanos = (int32_t)(versionMicroseconds % kMicrosPerSec) * kMillisPerSec;
-
- FIRTimestamp *timestamp = [[FIRTimestamp alloc] initWithSeconds:seconds nanoseconds:nanos];
- return [FSTSnapshotVersion versionWithTimestamp:timestamp];
-}
-
FSTDocument *FSTTestDoc(const absl::string_view path,
FSTTestSnapshotVersion version,
NSDictionary<NSString *, id> *data,
@@ -173,14 +155,14 @@ FSTDocument *FSTTestDoc(const absl::string_view path,
DocumentKey key = testutil::Key(path);
return [FSTDocument documentWithData:FSTTestObjectValue(data)
key:key
- version:FSTTestVersion(version)
+ version:testutil::Version(version)
hasLocalMutations:hasMutations];
}
FSTDeletedDocument *FSTTestDeletedDoc(const absl::string_view path,
FSTTestSnapshotVersion version) {
DocumentKey key = testutil::Key(path);
- return [FSTDeletedDocument documentWithKey:key version:FSTTestVersion(version)];
+ return [FSTDeletedDocument documentWithKey:key version:testutil::Version(version)];
}
FSTDocumentKeyReference *FSTTestRef(const absl::string_view projectID,
@@ -355,17 +337,17 @@ NSData *_Nullable FSTTestResumeTokenFromSnapshotVersion(FSTTestSnapshotVersion s
FSTLocalViewChanges *FSTTestViewChanges(FSTQuery *query,
NSArray<NSString *> *addedKeys,
NSArray<NSString *> *removedKeys) {
- FSTDocumentKeySet *added = [FSTDocumentKeySet keySet];
+ DocumentKeySet added;
for (NSString *keyPath in addedKeys) {
- FSTDocumentKey *key = FSTTestDocKey(keyPath);
- added = [added setByAddingObject:key];
+ added = added.insert(testutil::Key(util::MakeStringView(keyPath)));
}
- FSTDocumentKeySet *removed = [FSTDocumentKeySet keySet];
+ DocumentKeySet removed;
for (NSString *keyPath in removedKeys) {
- FSTDocumentKey *key = FSTTestDocKey(keyPath);
- removed = [removed setByAddingObject:key];
+ removed = removed.insert(testutil::Key(util::MakeStringView(keyPath)));
}
- return [FSTLocalViewChanges changesForQuery:query addedKeys:added removedKeys:removed];
+ return [FSTLocalViewChanges changesForQuery:query
+ addedKeys:std::move(added)
+ removedKeys:std::move(removed)];
}
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm
index 1817015..434b77b 100644
--- a/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm
+++ b/Firestore/Example/Tests/Util/FSTIntegrationTestCase.mm
@@ -17,7 +17,13 @@
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
#import <FirebaseCore/FIRLogger.h>
-#import <FirebaseFirestore/FirebaseFirestore-umbrella.h>
+#import <FirebaseFirestore/FIRCollectionReference.h>
+#import <FirebaseFirestore/FIRDocumentChange.h>
+#import <FirebaseFirestore/FIRDocumentReference.h>
+#import <FirebaseFirestore/FIRDocumentSnapshot.h>
+#import <FirebaseFirestore/FIRFirestoreSettings.h>
+#import <FirebaseFirestore/FIRQuerySnapshot.h>
+#import <FirebaseFirestore/FIRSnapshotMetadata.h>
#import <GRPCClient/GRPCCall+ChannelArg.h>
#import <GRPCClient/GRPCCall+Tests.h>
diff --git a/Firestore/Source/API/FIRDocumentReference.mm b/Firestore/Source/API/FIRDocumentReference.mm
index c2fc546..5ad606c 100644
--- a/Firestore/Source/API/FIRDocumentReference.mm
+++ b/Firestore/Source/API/FIRDocumentReference.mm
@@ -125,6 +125,11 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)setData:(NSDictionary<NSString *, id> *)documentData
+ mergeFields:(NSArray<id> *)mergeFields {
+ return [self setData:documentData mergeFields:mergeFields completion:nil];
+}
+
+- (void)setData:(NSDictionary<NSString *, id> *)documentData
completion:(nullable void (^)(NSError *_Nullable error))completion {
return [self setData:documentData merge:NO completion:completion];
}
@@ -132,8 +137,19 @@ NS_ASSUME_NONNULL_BEGIN
- (void)setData:(NSDictionary<NSString *, id> *)documentData
merge:(BOOL)merge
completion:(nullable void (^)(NSError *_Nullable error))completion {
- FSTParsedSetData *parsed = merge ? [self.firestore.dataConverter parsedMergeData:documentData]
- : [self.firestore.dataConverter parsedSetData:documentData];
+ FSTParsedSetData *parsed =
+ merge ? [self.firestore.dataConverter parsedMergeData:documentData fieldMask:nil]
+ : [self.firestore.dataConverter parsedSetData:documentData];
+ return [self.firestore.client
+ writeMutations:[parsed mutationsWithKey:self.key precondition:Precondition::None()]
+ completion:completion];
+}
+
+- (void)setData:(NSDictionary<NSString *, id> *)documentData
+ mergeFields:(NSArray<id> *)mergeFields
+ completion:(nullable void (^)(NSError *_Nullable error))completion {
+ FSTParsedSetData *parsed =
+ [self.firestore.dataConverter parsedMergeData:documentData fieldMask:mergeFields];
return [self.firestore.client
writeMutations:[parsed mutationsWithKey:self.key precondition:Precondition::None()]
completion:completion];
@@ -264,8 +280,8 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
};
FSTAsyncQueryListener *asyncListener =
- [[FSTAsyncQueryListener alloc] initWithDispatchQueue:self.firestore.client.userDispatchQueue
- snapshotHandler:snapshotHandler];
+ [[FSTAsyncQueryListener alloc] initWithExecutor:self.firestore.client.userExecutor
+ snapshotHandler:snapshotHandler];
FSTQueryListener *internalListener =
[firestore.client listenToQuery:query
diff --git a/Firestore/Source/API/FIRFieldPath.mm b/Firestore/Source/API/FIRFieldPath.mm
index d0d8714..4fd0022 100644
--- a/Firestore/Source/API/FIRFieldPath.mm
+++ b/Firestore/Source/API/FIRFieldPath.mm
@@ -25,6 +25,7 @@
#import "Firestore/Source/Util/FSTUsageValidation.h"
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/util/hashing.h"
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
namespace util = firebase::firestore::util;
@@ -114,7 +115,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (NSUInteger)hash {
- return _internalValue.Hash();
+ return util::Hash(_internalValue);
}
- (const firebase::firestore::model::FieldPath &)internalValue {
diff --git a/Firestore/Source/API/FIRFirestore.mm b/Firestore/Source/API/FIRFirestore.mm
index fe461d6..e5f0c12 100644
--- a/Firestore/Source/API/FIRFirestore.mm
+++ b/Firestore/Source/API/FIRFirestore.mm
@@ -45,12 +45,16 @@
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
#include "absl/memory/memory.h"
+#include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h"
+
namespace util = firebase::firestore::util;
using firebase::firestore::auth::CredentialsProvider;
using firebase::firestore::auth::FirebaseCredentialsProvider;
using firebase::firestore::core::DatabaseInfo;
using firebase::firestore::model::DatabaseId;
using firebase::firestore::model::ResourcePath;
+using util::internal::Executor;
+using util::internal::ExecutorLibdispatch;
NS_ASSUME_NONNULL_BEGIN
@@ -272,12 +276,13 @@ extern "C" NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
const DatabaseInfo database_info(*self.databaseID, util::MakeStringView(_persistenceKey),
util::MakeStringView(_settings.host), _settings.sslEnabled);
- FSTDispatchQueue *userDispatchQueue = [FSTDispatchQueue queueWith:_settings.dispatchQueue];
+ std::unique_ptr<Executor> userExecutor =
+ absl::make_unique<ExecutorLibdispatch>(_settings.dispatchQueue);
_client = [FSTFirestoreClient clientWithDatabaseInfo:database_info
usePersistence:_settings.persistenceEnabled
credentialsProvider:_credentialsProvider.get()
- userDispatchQueue:userDispatchQueue
+ userExecutor:std::move(userExecutor)
workerDispatchQueue:_workerDispatchQueue];
}
}
diff --git a/Firestore/Source/API/FIRQuery.mm b/Firestore/Source/API/FIRQuery.mm
index 32d8327..596f6ac 100644
--- a/Firestore/Source/API/FIRQuery.mm
+++ b/Firestore/Source/API/FIRQuery.mm
@@ -183,8 +183,8 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
};
FSTAsyncQueryListener *asyncListener =
- [[FSTAsyncQueryListener alloc] initWithDispatchQueue:self.firestore.client.userDispatchQueue
- snapshotHandler:snapshotHandler];
+ [[FSTAsyncQueryListener alloc] initWithExecutor:self.firestore.client.userExecutor
+ snapshotHandler:snapshotHandler];
FSTQueryListener *internalListener =
[firestore.client listenToQuery:query
@@ -456,8 +456,8 @@ addSnapshotListenerInternalWithOptions:(FSTListenOptions *)internalOptions
if (fieldPath.IsKeyFieldPath()) {
if (filterOperator == FSTRelationFilterOperatorArrayContains) {
FSTThrowInvalidArgument(
- @"Invalid query. You can't do arrayContains queries on document ID since document IDs "
- @"are not arrays.");
+ @"Invalid query. You can't perform arrayContains queries on document ID since document "
+ "IDs are not arrays.");
}
if ([value isKindOfClass:[NSString class]]) {
NSString *documentKey = (NSString *)value;
diff --git a/Firestore/Source/API/FIRTransaction.mm b/Firestore/Source/API/FIRTransaction.mm
index 668a359..b5bdefa 100644
--- a/Firestore/Source/API/FIRTransaction.mm
+++ b/Firestore/Source/API/FIRTransaction.mm
@@ -68,8 +68,19 @@ NS_ASSUME_NONNULL_BEGIN
forDocument:(FIRDocumentReference *)document
merge:(BOOL)merge {
[self validateReference:document];
- FSTParsedSetData *parsed = merge ? [self.firestore.dataConverter parsedMergeData:data]
- : [self.firestore.dataConverter parsedSetData:data];
+ FSTParsedSetData *parsed = merge
+ ? [self.firestore.dataConverter parsedMergeData:data fieldMask:nil]
+ : [self.firestore.dataConverter parsedSetData:data];
+ [self.internalTransaction setData:parsed forDocument:document.key];
+ return self;
+}
+
+- (FIRTransaction *)setData:(NSDictionary<NSString *, id> *)data
+ forDocument:(FIRDocumentReference *)document
+ mergeFields:(NSArray<id> *)mergeFields {
+ [self validateReference:document];
+ FSTParsedSetData *parsed =
+ [self.firestore.dataConverter parsedMergeData:data fieldMask:mergeFields];
[self.internalTransaction setData:parsed forDocument:document.key];
return self;
}
diff --git a/Firestore/Source/API/FIRWriteBatch.mm b/Firestore/Source/API/FIRWriteBatch.mm
index 1185dae..366c708 100644
--- a/Firestore/Source/API/FIRWriteBatch.mm
+++ b/Firestore/Source/API/FIRWriteBatch.mm
@@ -70,8 +70,21 @@ NS_ASSUME_NONNULL_BEGIN
merge:(BOOL)merge {
[self verifyNotCommitted];
[self validateReference:document];
- FSTParsedSetData *parsed = merge ? [self.firestore.dataConverter parsedMergeData:data]
- : [self.firestore.dataConverter parsedSetData:data];
+ FSTParsedSetData *parsed = merge
+ ? [self.firestore.dataConverter parsedMergeData:data fieldMask:nil]
+ : [self.firestore.dataConverter parsedSetData:data];
+ [self.mutations
+ addObjectsFromArray:[parsed mutationsWithKey:document.key precondition:Precondition::None()]];
+ return self;
+}
+
+- (FIRWriteBatch *)setData:(NSDictionary<NSString *, id> *)data
+ forDocument:(FIRDocumentReference *)document
+ mergeFields:(NSArray<id> *)mergeFields {
+ [self verifyNotCommitted];
+ [self validateReference:document];
+ FSTParsedSetData *parsed =
+ [self.firestore.dataConverter parsedMergeData:data fieldMask:mergeFields];
[self.mutations
addObjectsFromArray:[parsed mutationsWithKey:document.key precondition:Precondition::None()]];
return self;
diff --git a/Firestore/Source/API/FSTUserDataConverter.h b/Firestore/Source/API/FSTUserDataConverter.h
index 98a65ae..27a5f09 100644
--- a/Firestore/Source/API/FSTUserDataConverter.h
+++ b/Firestore/Source/API/FSTUserDataConverter.h
@@ -27,7 +27,6 @@
@class FSTObjectValue;
@class FSTFieldValue;
@class FSTMutation;
-@class FSTSnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
@@ -130,7 +129,7 @@ typedef id _Nullable (^FSTPreConverterBlock)(id _Nullable);
- (FSTParsedSetData *)parsedSetData:(id)input;
/** Parse document data from a setData call with `merge:YES`. */
-- (FSTParsedSetData *)parsedMergeData:(id)input;
+- (FSTParsedSetData *)parsedMergeData:(id)input fieldMask:(nullable NSArray<id> *)fieldMask;
/** Parse update data from an updateData call. */
- (FSTParsedUpdateData *)parsedUpdateData:(id)input;
diff --git a/Firestore/Source/API/FSTUserDataConverter.mm b/Firestore/Source/API/FSTUserDataConverter.mm
index 2794398..6d01c75 100644
--- a/Firestore/Source/API/FSTUserDataConverter.mm
+++ b/Firestore/Source/API/FSTUserDataConverter.mm
@@ -412,7 +412,7 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
return self;
}
-- (FSTParsedSetData *)parsedMergeData:(id)input {
+- (FSTParsedSetData *)parsedMergeData:(id)input fieldMask:(nullable NSArray<id> *)fieldMask {
// NOTE: The public API is typed as NSDictionary but we type 'input' as 'id' since we can't trust
// Obj-C to verify the type for us.
if (![input isKindOfClass:[NSDictionary class]]) {
@@ -424,9 +424,45 @@ typedef NS_ENUM(NSInteger, FSTUserDataSource) {
path:absl::make_unique<FieldPath>(FieldPath::EmptyPath())];
FSTObjectValue *updateData = (FSTObjectValue *)[self parseData:input context:context];
+ FieldMask convertedFieldMask;
+ std::vector<FieldTransform> convertedFieldTransform;
+
+ if (fieldMask) {
+ __block std::vector<FieldPath> fieldMaskPaths{};
+ [fieldMask enumerateObjectsUsingBlock:^(id fieldPath, NSUInteger idx, BOOL *stop) {
+ FieldPath path{};
+
+ if ([fieldPath isKindOfClass:[NSString class]]) {
+ path = [FIRFieldPath pathWithDotSeparatedString:fieldPath].internalValue;
+ } else if ([fieldPath isKindOfClass:[FIRFieldPath class]]) {
+ path = ((FIRFieldPath *)fieldPath).internalValue;
+ } else {
+ FSTThrowInvalidArgument(
+ @"All elements in mergeFields: must be NSStrings or FIRFieldPaths.");
+ }
+
+ if ([updateData valueForPath:path] == nil) {
+ FSTThrowInvalidArgument(
+ @"Field '%s' is specified in your field mask but missing from your input data.",
+ path.CanonicalString().c_str());
+ }
+
+ fieldMaskPaths.push_back(path);
+ }];
+ convertedFieldMask = FieldMask(fieldMaskPaths);
+ std::copy_if(context.fieldTransforms->begin(), context.fieldTransforms->end(),
+ std::back_inserter(convertedFieldTransform),
+ [&](const FieldTransform &fieldTransform) {
+ return convertedFieldMask.covers(fieldTransform.path());
+ });
+ } else {
+ convertedFieldMask = FieldMask{*context.fieldMask};
+ convertedFieldTransform = *context.fieldTransforms;
+ }
+
return [[FSTParsedSetData alloc] initWithData:updateData
- fieldMask:FieldMask{*context.fieldMask}
- fieldTransforms:*context.fieldTransforms];
+ fieldMask:convertedFieldMask
+ fieldTransforms:convertedFieldTransform];
}
- (FSTParsedSetData *)parsedSetData:(id)input {
diff --git a/Firestore/Source/Core/FSTFirestoreClient.h b/Firestore/Source/Core/FSTFirestoreClient.h
index 7285e65..94c2284 100644
--- a/Firestore/Source/Core/FSTFirestoreClient.h
+++ b/Firestore/Source/Core/FSTFirestoreClient.h
@@ -15,6 +15,7 @@
*/
#import <Foundation/Foundation.h>
+#include <memory>
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Core/FSTViewSnapshot.h"
@@ -23,6 +24,7 @@
#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
#include "Firestore/core/src/firebase/firestore/core/database_info.h"
#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/util/executor.h"
@class FIRDocumentReference;
@class FIRDocumentSnapshot;
@@ -50,14 +52,15 @@ NS_ASSUME_NONNULL_BEGIN
/**
* Creates and returns a FSTFirestoreClient with the given parameters.
*
- * All callbacks and events will be triggered on the provided userDispatchQueue.
+ * All callbacks and events will be triggered on the provided userExecutor.
*/
-+ (instancetype)clientWithDatabaseInfo:(const firebase::firestore::core::DatabaseInfo &)databaseInfo
- usePersistence:(BOOL)usePersistence
- credentialsProvider:(firebase::firestore::auth::CredentialsProvider *)
- credentialsProvider // no passing ownership
- userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
- workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue;
++ (instancetype)
+clientWithDatabaseInfo:(const firebase::firestore::core::DatabaseInfo &)databaseInfo
+ usePersistence:(BOOL)usePersistence
+ credentialsProvider:(firebase::firestore::auth::CredentialsProvider *)
+ credentialsProvider // no passing ownership
+ userExecutor:(std::unique_ptr<firebase::firestore::util::internal::Executor>)userExecutor
+ workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue;
- (instancetype)init __attribute__((unavailable("Use static constructor method.")));
@@ -111,7 +114,7 @@ NS_ASSUME_NONNULL_BEGIN
* Dispatch queue for user callbacks / events. This will often be the "Main Dispatch Queue" of the
* app but the developer can configure it to a different queue if they so choose.
*/
-@property(nonatomic, strong, readonly) FSTDispatchQueue *userDispatchQueue;
+- (firebase::firestore::util::internal::Executor *)userExecutor;
@end
diff --git a/Firestore/Source/Core/FSTFirestoreClient.mm b/Firestore/Source/Core/FSTFirestoreClient.mm
index 4f1a20b..cede958 100644
--- a/Firestore/Source/Core/FSTFirestoreClient.mm
+++ b/Firestore/Source/Core/FSTFirestoreClient.mm
@@ -18,6 +18,7 @@
#include <future> // NOLINT(build/c++11)
#include <memory>
+#include <utility>
#import "FIRFirestoreErrors.h"
#import "Firestore/Source/API/FIRDocumentReference+Internal.h"
@@ -56,6 +57,9 @@ using firebase::firestore::auth::CredentialsProvider;
using firebase::firestore::auth::User;
using firebase::firestore::core::DatabaseInfo;
using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::DocumentKeySet;
+
+using firebase::firestore::util::internal::Executor;
NS_ASSUME_NONNULL_BEGIN
@@ -67,7 +71,7 @@ NS_ASSUME_NONNULL_BEGIN
usePersistence:(BOOL)usePersistence
credentialsProvider:
(CredentialsProvider *)credentialsProvider // no passing ownership
- userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
+ userExecutor:(std::unique_ptr<Executor>)userExecutor
workerDispatchQueue:(FSTDispatchQueue *)queue NS_DESIGNATED_INITIALIZER;
@property(nonatomic, assign, readonly) const DatabaseInfo *databaseInfo;
@@ -90,18 +94,24 @@ NS_ASSUME_NONNULL_BEGIN
@end
-@implementation FSTFirestoreClient
+@implementation FSTFirestoreClient {
+ std::unique_ptr<Executor> _userExecutor;
+}
+
+- (Executor *)userExecutor {
+ return _userExecutor.get();
+}
+ (instancetype)clientWithDatabaseInfo:(const DatabaseInfo &)databaseInfo
usePersistence:(BOOL)usePersistence
credentialsProvider:
(CredentialsProvider *)credentialsProvider // no passing ownership
- userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
+ userExecutor:(std::unique_ptr<Executor>)userExecutor
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue {
return [[FSTFirestoreClient alloc] initWithDatabaseInfo:databaseInfo
usePersistence:usePersistence
credentialsProvider:credentialsProvider
- userDispatchQueue:userDispatchQueue
+ userExecutor:std::move(userExecutor)
workerDispatchQueue:workerDispatchQueue];
}
@@ -109,12 +119,12 @@ NS_ASSUME_NONNULL_BEGIN
usePersistence:(BOOL)usePersistence
credentialsProvider:
(CredentialsProvider *)credentialsProvider // no passing ownership
- userDispatchQueue:(FSTDispatchQueue *)userDispatchQueue
+ userExecutor:(std::unique_ptr<Executor>)userExecutor
workerDispatchQueue:(FSTDispatchQueue *)workerDispatchQueue {
if (self = [super init]) {
_databaseInfo = databaseInfo;
_credentialsProvider = credentialsProvider;
- _userDispatchQueue = userDispatchQueue;
+ _userExecutor = std::move(userExecutor);
_workerDispatchQueue = workerDispatchQueue;
auto userPromise = std::make_shared<std::promise<User>>();
@@ -229,9 +239,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.workerDispatchQueue dispatchAsync:^{
[self.remoteStore disableNetwork];
if (completion) {
- [self.userDispatchQueue dispatchAsync:^{
- completion(nil);
- }];
+ self->_userExecutor->Execute([=] { completion(nil); });
}
}];
}
@@ -240,9 +248,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.workerDispatchQueue dispatchAsync:^{
[self.remoteStore enableNetwork];
if (completion) {
- [self.userDispatchQueue dispatchAsync:^{
- completion(nil);
- }];
+ self->_userExecutor->Execute([=] { completion(nil); });
}
}];
}
@@ -254,9 +260,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.remoteStore shutdown];
[self.persistence shutdown];
if (completion) {
- [self.userDispatchQueue dispatchAsync:^{
- completion(nil);
- }];
+ self->_userExecutor->Execute([=] { completion(nil); });
}
}];
}
@@ -311,11 +315,9 @@ NS_ASSUME_NONNULL_BEGIN
completion:(void (^)(FIRQuerySnapshot *_Nullable query,
NSError *_Nullable error))completion {
[self.workerDispatchQueue dispatchAsync:^{
-
FSTDocumentDictionary *docs = [self.localStore executeQuery:query.query];
- FSTDocumentKeySet *remoteKeys = [FSTDocumentKeySet keySet];
- FSTView *view = [[FSTView alloc] initWithQuery:query.query remoteDocuments:remoteKeys];
+ FSTView *view = [[FSTView alloc] initWithQuery:query.query remoteDocuments:DocumentKeySet{}];
FSTViewDocumentChanges *viewDocChanges = [view computeChangesWithDocuments:docs];
FSTViewChange *viewChange = [view applyChangesToDocuments:viewDocChanges];
FSTAssert(viewChange.limboChanges.count == 0,
@@ -339,18 +341,14 @@ NS_ASSUME_NONNULL_BEGIN
[self.workerDispatchQueue dispatchAsync:^{
if (mutations.count == 0) {
if (completion) {
- [self.userDispatchQueue dispatchAsync:^{
- completion(nil);
- }];
+ self->_userExecutor->Execute([=] { completion(nil); });
}
} else {
[self.syncEngine writeMutations:mutations
completion:^(NSError *error) {
// Dispatch the result back onto the user dispatch queue.
if (completion) {
- [self.userDispatchQueue dispatchAsync:^{
- completion(error);
- }];
+ self->_userExecutor->Execute([=] { completion(error); });
}
}];
}
@@ -361,17 +359,16 @@ NS_ASSUME_NONNULL_BEGIN
updateBlock:(FSTTransactionBlock)updateBlock
completion:(FSTVoidIDErrorBlock)completion {
[self.workerDispatchQueue dispatchAsync:^{
- [self.syncEngine transactionWithRetries:retries
- workerDispatchQueue:self.workerDispatchQueue
- updateBlock:updateBlock
- completion:^(id _Nullable result, NSError *_Nullable error) {
- // Dispatch the result back onto the user dispatch queue.
- if (completion) {
- [self.userDispatchQueue dispatchAsync:^{
- completion(result, error);
- }];
- }
- }];
+ [self.syncEngine
+ transactionWithRetries:retries
+ workerDispatchQueue:self.workerDispatchQueue
+ updateBlock:updateBlock
+ completion:^(id _Nullable result, NSError *_Nullable error) {
+ // Dispatch the result back onto the user dispatch queue.
+ if (completion) {
+ self->_userExecutor->Execute([=] { completion(result, error); });
+ }
+ }];
}];
}
diff --git a/Firestore/Source/Core/FSTQuery.mm b/Firestore/Source/Core/FSTQuery.mm
index 0cd11e8..d3961e8 100644
--- a/Firestore/Source/Core/FSTQuery.mm
+++ b/Firestore/Source/Core/FSTQuery.mm
@@ -28,6 +28,7 @@
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/hashing.h"
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
namespace util = firebase::firestore::util;
@@ -259,7 +260,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
}
- (NSUInteger)hash {
- return _field.Hash();
+ return util::Hash(_field);
}
@end
@@ -305,7 +306,7 @@ NSString *FSTStringFromQueryRelationOperator(FSTRelationFilterOperator filterOpe
}
- (NSUInteger)hash {
- return _field.Hash();
+ return util::Hash(_field);
}
@end
diff --git a/Firestore/Source/Core/FSTSnapshotVersion.h b/Firestore/Source/Core/FSTSnapshotVersion.h
deleted file mode 100644
index 8649d40..0000000
--- a/Firestore/Source/Core/FSTSnapshotVersion.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2017 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#import <Foundation/Foundation.h>
-
-NS_ASSUME_NONNULL_BEGIN
-
-@class FIRTimestamp;
-
-/**
- * A version of a document in Firestore. This corresponds to the version timestamp, such as
- * update_time or read_time.
- */
-@interface FSTSnapshotVersion : NSObject <NSCopying>
-
-/** Creates a new version that is smaller than all other versions. */
-+ (instancetype)noVersion;
-
-/** Creates a new version representing the given timestamp. */
-+ (instancetype)versionWithTimestamp:(FIRTimestamp *)timestamp;
-
-- (instancetype)init NS_UNAVAILABLE;
-
-- (NSComparisonResult)compare:(FSTSnapshotVersion *)other;
-
-@property(nonatomic, strong, readonly) FIRTimestamp *timestamp;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTSnapshotVersion.mm b/Firestore/Source/Core/FSTSnapshotVersion.mm
deleted file mode 100644
index 58b2be4..0000000
--- a/Firestore/Source/Core/FSTSnapshotVersion.mm
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright 2017 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-
-#import "FIRTimestamp.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@implementation FSTSnapshotVersion
-
-+ (instancetype)noVersion {
- static FSTSnapshotVersion *min;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- FIRTimestamp *timestamp = [[FIRTimestamp alloc] initWithSeconds:0 nanoseconds:0];
- min = [FSTSnapshotVersion versionWithTimestamp:timestamp];
- });
- return min;
-}
-
-+ (instancetype)versionWithTimestamp:(FIRTimestamp *)timestamp {
- return [[FSTSnapshotVersion alloc] initWithTimestamp:timestamp];
-}
-
-- (instancetype)initWithTimestamp:(FIRTimestamp *)timestamp {
- self = [super init];
- if (self) {
- _timestamp = timestamp;
- }
- return self;
-}
-
-#pragma mark - NSObject methods
-
-- (BOOL)isEqual:(id)object {
- if (self == object) {
- return YES;
- }
- if (![object isKindOfClass:[FSTSnapshotVersion class]]) {
- return NO;
- }
- return [self.timestamp isEqual:((FSTSnapshotVersion *)object).timestamp];
-}
-
-- (NSUInteger)hash {
- return self.timestamp.hash;
-}
-
-- (NSString *)description {
- return [NSString stringWithFormat:@"<FSTSnapshotVersion: %@>", self.timestamp];
-}
-
-- (id)copyWithZone:(NSZone *_Nullable)zone {
- // Implements NSCopying without actually copying because timestamps are immutable.
- return self;
-}
-
-#pragma mark - Public methods
-
-- (NSComparisonResult)compare:(FSTSnapshotVersion *)other {
- return [self.timestamp compare:other.timestamp];
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Core/FSTSyncEngine.mm b/Firestore/Source/Core/FSTSyncEngine.mm
index 138fb41..ed97d6c 100644
--- a/Firestore/Source/Core/FSTSyncEngine.mm
+++ b/Firestore/Source/Core/FSTSyncEngine.mm
@@ -21,10 +21,10 @@
#include <map>
#include <set>
#include <unordered_map>
+#include <utility>
#import "FIRFirestoreErrors.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Core/FSTTransaction.h"
#import "Firestore/Source/Core/FSTView.h"
#import "Firestore/Source/Core/FSTViewSnapshot.h"
@@ -45,12 +45,15 @@
#include "Firestore/core/src/firebase/firestore/auth/user.h"
#include "Firestore/core/src/firebase/firestore/core/target_id_generator.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
using firebase::firestore::auth::HashUser;
using firebase::firestore::auth::User;
using firebase::firestore::core::TargetIdGenerator;
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::SnapshotVersion;
using firebase::firestore::model::TargetId;
+using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
@@ -184,9 +187,9 @@ static const FSTListenSequenceNumber kIrrelevantSequenceNumber = -1;
FSTQueryData *queryData = [self.localStore allocateQuery:query];
FSTDocumentDictionary *docs = [self.localStore executeQuery:query];
- FSTDocumentKeySet *remoteKeys = [self.localStore remoteDocumentKeysForTarget:queryData.targetID];
+ DocumentKeySet remoteKeys = [self.localStore remoteDocumentKeysForTarget:queryData.targetID];
- FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:remoteKeys];
+ FSTView *view = [[FSTView alloc] initWithQuery:query remoteDocuments:std::move(remoteKeys)];
FSTViewDocumentChanges *viewDocChanges = [view computeChangesWithDocuments:docs];
FSTViewChange *viewChange = [view applyChangesToDocuments:viewDocChanges];
FSTAssert(viewChange.limboChanges.count == 0,
@@ -301,8 +304,7 @@ static const FSTListenSequenceNumber kIrrelevantSequenceNumber = -1;
if (iter == self->_limboKeysByTarget.end()) {
FSTQueryView *qv = self.queryViewsByTarget[targetID];
FSTAssert(qv, @"Missing queryview for non-limbo query: %i", [targetID intValue]);
- [remoteEvent filterUpdatesFromTargetChange:targetChange
- existingDocuments:qv.view.syncedDocuments];
+ [targetChange.mapping filterUpdatesUsingExistingKeys:qv.view.syncedDocuments];
} else {
[remoteEvent synthesizeDeleteForLimboTargetChange:targetChange key:iter->second];
}
@@ -346,10 +348,15 @@ static const FSTListenSequenceNumber kIrrelevantSequenceNumber = -1;
NSMutableDictionary<NSNumber *, FSTTargetChange *> *targetChanges =
[NSMutableDictionary dictionary];
FSTDeletedDocument *doc =
- [FSTDeletedDocument documentWithKey:limboKey version:[FSTSnapshotVersion noVersion]];
- FSTRemoteEvent *event = [FSTRemoteEvent eventWithSnapshotVersion:[FSTSnapshotVersion noVersion]
- targetChanges:targetChanges
- documentUpdates:{{limboKey, doc}}];
+ [FSTDeletedDocument documentWithKey:limboKey version:SnapshotVersion::None()];
+ DocumentKeySet limboDocuments = DocumentKeySet{doc.key};
+ FSTRemoteEvent *event =
+ [[FSTRemoteEvent alloc] initWithSnapshotVersion:SnapshotVersion::None()
+ targetChanges:targetChanges
+ documentUpdates:{
+ { limboKey, doc }
+ }
+ limboDocuments:std::move(limboDocuments)];
[self applyRemoteEvent:event];
} else {
FSTQueryView *queryView = self.queryViewsByTarget[@(targetID)];
diff --git a/Firestore/Source/Core/FSTTransaction.mm b/Firestore/Source/Core/FSTTransaction.mm
index 4aabd5a..5c36b20 100644
--- a/Firestore/Source/Core/FSTTransaction.mm
+++ b/Firestore/Source/Core/FSTTransaction.mm
@@ -23,19 +23,21 @@
#import "FIRFirestoreErrors.h"
#import "Firestore/Source/API/FSTUserDataConverter.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Model/FSTDocument.h"
-#import "Firestore/Source/Model/FSTDocumentKeySet.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Remote/FSTDatastore.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTUsageValidation.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::Precondition;
+using firebase::firestore::model::SnapshotVersion;
+using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
@@ -53,7 +55,7 @@ NS_ASSUME_NONNULL_BEGIN
@end
@implementation FSTTransaction {
- std::map<DocumentKey, FSTSnapshotVersion *> _readVersions;
+ std::map<DocumentKey, SnapshotVersion> _readVersions;
}
+ (instancetype)transactionWithDatastore:(FSTDatastore *)datastore {
@@ -79,11 +81,11 @@ NS_ASSUME_NONNULL_BEGIN
- (BOOL)recordVersionForDocument:(FSTMaybeDocument *)doc error:(NSError **)error {
FSTAssert(error != nil, @"nil error parameter");
*error = nil;
- FSTSnapshotVersion *docVersion = doc.version;
+ SnapshotVersion docVersion = doc.version;
if ([doc isKindOfClass:[FSTDeletedDocument class]]) {
// For deleted docs, we must record an explicit no version to build the right precondition
// when writing.
- docVersion = [FSTSnapshotVersion noVersion];
+ docVersion = SnapshotVersion::None();
}
if (_readVersions.find(doc.key) == _readVersions.end()) {
_readVersions[doc.key] = docVersion;
@@ -159,8 +161,8 @@ NS_ASSUME_NONNULL_BEGIN
return Precondition::Exists(true);
}
- FSTSnapshotVersion *version = iter->second;
- if ([version isEqual:[FSTSnapshotVersion noVersion]]) {
+ const SnapshotVersion &version = iter->second;
+ if (version == SnapshotVersion::None()) {
// The document was read, but doesn't exist.
// Return an error because the precondition is impossible
if (error) {
@@ -200,7 +202,7 @@ NS_ASSUME_NONNULL_BEGIN
precondition:[self preconditionForDocumentKey:key]] ]];
// Since the delete will be applied before all following writes, we need to ensure that the
// precondition for the next write will be exists without timestamp.
- _readVersions[key] = [FSTSnapshotVersion noVersion];
+ _readVersions[key] = SnapshotVersion::None();
}
- (void)commitWithCompletion:(FSTVoidErrorBlock)completion {
@@ -215,15 +217,15 @@ NS_ASSUME_NONNULL_BEGIN
}
// Make a list of read documents that haven't been written.
- FSTDocumentKeySet *unwritten = [FSTDocumentKeySet keySet];
+ DocumentKeySet unwritten;
for (const auto &kv : _readVersions) {
- unwritten = [unwritten setByAddingObject:kv.first];
+ unwritten = unwritten.insert(kv.first);
};
// For each mutation, note that the doc was written.
for (FSTMutation *mutation in self.mutations) {
- unwritten = [unwritten setByRemovingObject:mutation.key];
+ unwritten = unwritten.erase(mutation.key);
}
- if (unwritten.count) {
+ if (!unwritten.empty()) {
// TODO(klimt): This is a temporary restriction, until "verify" is supported on the backend.
completion([NSError
errorWithDomain:FIRFirestoreErrorDomain
diff --git a/Firestore/Source/Core/FSTView.h b/Firestore/Source/Core/FSTView.h
index 431b863..fc6cead 100644
--- a/Firestore/Source/Core/FSTView.h
+++ b/Firestore/Source/Core/FSTView.h
@@ -18,9 +18,9 @@
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
-#import "Firestore/Source/Model/FSTDocumentKeySet.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
@class FSTDocumentSet;
@class FSTDocumentViewChangeSet;
@@ -38,6 +38,8 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init NS_UNAVAILABLE;
+- (const firebase::firestore::model::DocumentKeySet &)mutatedKeys;
+
/** The new set of docs that should be in the view. */
@property(nonatomic, strong, readonly) FSTDocumentSet *documentSet;
@@ -50,8 +52,6 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property(nonatomic, assign, readonly) BOOL needsRefill;
-@property(nonatomic, strong, readonly) FSTDocumentKeySet *mutatedKeys;
-
@end
#pragma mark - FSTLimboDocumentChange
@@ -97,7 +97,8 @@ typedef NS_ENUM(NSInteger, FSTLimboDocumentChangeType) {
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithQuery:(FSTQuery *)query
- remoteDocuments:(FSTDocumentKeySet *)remoteDocuments NS_DESIGNATED_INITIALIZER;
+ remoteDocuments:(firebase::firestore::model::DocumentKeySet)remoteDocuments
+ NS_DESIGNATED_INITIALIZER;
/**
* Iterates over a set of doc changes, applies the query limit, and computes what the new results
@@ -152,7 +153,7 @@ typedef NS_ENUM(NSInteger, FSTLimboDocumentChangeType) {
* The set of remote documents that the server has told us belongs to the target associated with
* this view.
*/
-@property(nonatomic, strong, readonly) FSTDocumentKeySet *syncedDocuments;
+- (const firebase::firestore::model::DocumentKeySet &)syncedDocuments;
@end
diff --git a/Firestore/Source/Core/FSTView.mm b/Firestore/Source/Core/FSTView.mm
index d87951a..d254a82 100644
--- a/Firestore/Source/Core/FSTView.mm
+++ b/Firestore/Source/Core/FSTView.mm
@@ -29,6 +29,7 @@
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
@@ -40,26 +41,32 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithDocumentSet:(FSTDocumentSet *)documentSet
changeSet:(FSTDocumentViewChangeSet *)changeSet
needsRefill:(BOOL)needsRefill
- mutatedKeys:(FSTDocumentKeySet *)mutatedKeys NS_DESIGNATED_INITIALIZER;
+ mutatedKeys:(DocumentKeySet)mutatedKeys NS_DESIGNATED_INITIALIZER;
@end
-@implementation FSTViewDocumentChanges
+@implementation FSTViewDocumentChanges {
+ DocumentKeySet _mutatedKeys;
+}
- (instancetype)initWithDocumentSet:(FSTDocumentSet *)documentSet
changeSet:(FSTDocumentViewChangeSet *)changeSet
needsRefill:(BOOL)needsRefill
- mutatedKeys:(FSTDocumentKeySet *)mutatedKeys {
+ mutatedKeys:(DocumentKeySet)mutatedKeys {
self = [super init];
if (self) {
_documentSet = documentSet;
_changeSet = changeSet;
_needsRefill = needsRefill;
- _mutatedKeys = mutatedKeys;
+ _mutatedKeys = std::move(mutatedKeys);
}
return self;
}
+- (const DocumentKeySet &)mutatedKeys {
+ return _mutatedKeys;
+}
+
@end
#pragma mark - FSTLimboDocumentChange
@@ -165,32 +172,33 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
@property(nonatomic, strong) FSTDocumentSet *documentSet;
-/** Documents included in the remote target. */
-@property(nonatomic, strong) FSTDocumentKeySet *syncedDocuments;
-
-/** Documents in the view but not in the remote target */
-@property(nonatomic, strong) FSTDocumentKeySet *limboDocuments;
+@end
-/** Document Keys that have local changes. */
-@property(nonatomic, strong) FSTDocumentKeySet *mutatedKeys;
+@implementation FSTView {
+ /** Documents included in the remote target. */
+ DocumentKeySet _syncedDocuments;
-@end
+ /** Documents in the view but not in the remote target */
+ DocumentKeySet _limboDocuments;
-@implementation FSTView
+ /** Document Keys that have local changes. */
+ DocumentKeySet _mutatedKeys;
+}
-- (instancetype)initWithQuery:(FSTQuery *)query
- remoteDocuments:(nonnull FSTDocumentKeySet *)remoteDocuments {
+- (instancetype)initWithQuery:(FSTQuery *)query remoteDocuments:(DocumentKeySet)remoteDocuments {
self = [super init];
if (self) {
_query = query;
_documentSet = [FSTDocumentSet documentSetWithComparator:query.comparator];
- _syncedDocuments = remoteDocuments;
- _limboDocuments = [FSTDocumentKeySet keySet];
- _mutatedKeys = [FSTDocumentKeySet keySet];
+ _syncedDocuments = std::move(remoteDocuments);
}
return self;
}
+- (const DocumentKeySet &)syncedDocuments {
+ return _syncedDocuments;
+}
+
- (FSTViewDocumentChanges *)computeChangesWithDocuments:(FSTMaybeDocumentDictionary *)docChanges {
return [self computeChangesWithDocuments:docChanges previousChanges:nil];
}
@@ -202,8 +210,8 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
previousChanges ? previousChanges.changeSet : [FSTDocumentViewChangeSet changeSet];
FSTDocumentSet *oldDocumentSet = previousChanges ? previousChanges.documentSet : self.documentSet;
- __block FSTDocumentKeySet *newMutatedKeys =
- previousChanges ? previousChanges.mutatedKeys : self.mutatedKeys;
+ __block DocumentKeySet newMutatedKeys =
+ previousChanges ? previousChanges.mutatedKeys : _mutatedKeys;
__block FSTDocumentSet *newDocumentSet = oldDocumentSet;
__block BOOL needsRefill = NO;
@@ -236,13 +244,13 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
if (newDoc) {
newDocumentSet = [newDocumentSet documentSetByAddingDocument:newDoc];
if (newDoc.hasLocalMutations) {
- newMutatedKeys = [newMutatedKeys setByAddingObject:key];
+ newMutatedKeys = newMutatedKeys.insert(key);
} else {
- newMutatedKeys = [newMutatedKeys setByRemovingObject:key];
+ newMutatedKeys = newMutatedKeys.erase(key);
}
} else {
newDocumentSet = [newDocumentSet documentSetByRemovingKey:key];
- newMutatedKeys = [newMutatedKeys setByRemovingObject:key];
+ newMutatedKeys = newMutatedKeys.erase(key);
}
// Calculate change
@@ -311,7 +319,7 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
FSTDocumentSet *oldDocuments = self.documentSet;
self.documentSet = docChanges.documentSet;
- self.mutatedKeys = docChanges.mutatedKeys;
+ _mutatedKeys = docChanges.mutatedKeys;
// Sort changes based on type and query comparator.
NSArray<FSTDocumentViewChange *> *changes = [docChanges.changeSet changes];
@@ -325,7 +333,7 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
}];
[self applyTargetChange:targetChange];
NSArray<FSTLimboDocumentChange *> *limboChanges = [self updateLimboDocuments];
- BOOL synced = self.limboDocuments.count == 0 && self.isCurrent;
+ BOOL synced = _limboDocuments.empty() && self.isCurrent;
FSTSyncState newSyncState = synced ? FSTSyncStateSynced : FSTSyncStateLocal;
BOOL syncStateChanged = newSyncState != self.syncState;
self.syncState = newSyncState;
@@ -340,7 +348,7 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
oldDocuments:oldDocuments
documentChanges:changes
fromCache:newSyncState == FSTSyncStateLocal
- hasPendingWrites:!docChanges.mutatedKeys.isEmpty
+ hasPendingWrites:!docChanges.mutatedKeys.empty()
syncStateChanged:syncStateChanged];
return [FSTViewChange changeWithSnapshot:snapshot limboChanges:limboChanges];
@@ -358,7 +366,7 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
initWithDocumentSet:self.documentSet
changeSet:[FSTDocumentViewChangeSet changeSet]
needsRefill:NO
- mutatedKeys:self.mutatedKeys]];
+ mutatedKeys:_mutatedKeys]];
} else {
// No effect, just return a no-op FSTViewChange.
return [[FSTViewChange alloc] initWithSnapshot:nil limboChanges:@[]];
@@ -370,7 +378,7 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
/** Returns whether the doc for the given key should be in limbo. */
- (BOOL)shouldBeLimboDocumentKey:(const DocumentKey &)key {
// If the remote end says it's part of this query, it's not in limbo.
- if ([self.syncedDocuments containsObject:key]) {
+ if (_syncedDocuments.contains(key)) {
return NO;
}
// The local store doesn't think it's a result, so it shouldn't be in limbo.
@@ -395,16 +403,14 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
if (targetChange) {
FSTTargetMapping *targetMapping = targetChange.mapping;
if ([targetMapping isKindOfClass:[FSTResetMapping class]]) {
- self.syncedDocuments = ((FSTResetMapping *)targetMapping).documents;
+ _syncedDocuments = ((FSTResetMapping *)targetMapping).documents;
} else if ([targetMapping isKindOfClass:[FSTUpdateMapping class]]) {
- [((FSTUpdateMapping *)targetMapping).addedDocuments
- enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
- self.syncedDocuments = [self.syncedDocuments setByAddingObject:key];
- }];
- [((FSTUpdateMapping *)targetMapping).removedDocuments
- enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
- self.syncedDocuments = [self.syncedDocuments setByRemovingObject:key];
- }];
+ for (const DocumentKey &key : ((FSTUpdateMapping *)targetMapping).addedDocuments) {
+ _syncedDocuments = _syncedDocuments.insert(key);
+ }
+ for (const DocumentKey &key : ((FSTUpdateMapping *)targetMapping).removedDocuments) {
+ _syncedDocuments = _syncedDocuments.erase(key);
+ }
}
switch (targetChange.currentStatusUpdate) {
@@ -428,29 +434,29 @@ static NSComparisonResult FSTCompareDocumentViewChangeTypes(FSTDocumentViewChang
}
// TODO(klimt): Do this incrementally so that it's not quadratic when updating many documents.
- FSTDocumentKeySet *oldLimboDocuments = self.limboDocuments;
- self.limboDocuments = [FSTDocumentKeySet keySet];
+ DocumentKeySet oldLimboDocuments = std::move(_limboDocuments);
+ _limboDocuments = DocumentKeySet{};
for (FSTDocument *doc in self.documentSet.documentEnumerator) {
if ([self shouldBeLimboDocumentKey:doc.key]) {
- self.limboDocuments = [self.limboDocuments setByAddingObject:doc.key];
+ _limboDocuments = _limboDocuments.insert(doc.key);
}
}
// Diff the new limbo docs with the old limbo docs.
NSMutableArray<FSTLimboDocumentChange *> *changes =
- [NSMutableArray arrayWithCapacity:(oldLimboDocuments.count + self.limboDocuments.count)];
- [oldLimboDocuments enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
- if (![self.limboDocuments containsObject:key]) {
+ [NSMutableArray arrayWithCapacity:(oldLimboDocuments.size() + _limboDocuments.size())];
+ for (const DocumentKey &key : oldLimboDocuments) {
+ if (!_limboDocuments.contains(key)) {
[changes addObject:[FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeRemoved
key:key]];
}
- }];
- [self.limboDocuments enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
- if (![oldLimboDocuments containsObject:key]) {
+ }
+ for (const DocumentKey &key : _limboDocuments) {
+ if (!oldLimboDocuments.contains(key)) {
[changes addObject:[FSTLimboDocumentChange changeWithType:FSTLimboDocumentChangeTypeAdded
key:key]];
}
- }];
+ }
return changes;
}
diff --git a/Firestore/Source/Local/FSTLevelDB.mm b/Firestore/Source/Local/FSTLevelDB.mm
index fae85e7..bc2f2eb 100644
--- a/Firestore/Source/Local/FSTLevelDB.mm
+++ b/Firestore/Source/Local/FSTLevelDB.mm
@@ -243,6 +243,10 @@ using leveldb::WriteOptions;
_ptr.reset();
}
+- (_Nullable id<FSTReferenceDelegate>)referenceDelegate {
+ return nil;
+}
+
#pragma mark - Error and Status
+ (nullable NSError *)errorWithStatus:(Status)status description:(NSString *)description, ... {
diff --git a/Firestore/Source/Local/FSTLevelDBMutationQueue.mm b/Firestore/Source/Local/FSTLevelDBMutationQueue.mm
index 75c3cf6..2c9f68d 100644
--- a/Firestore/Source/Local/FSTLevelDBMutationQueue.mm
+++ b/Firestore/Source/Local/FSTLevelDBMutationQueue.mm
@@ -511,6 +511,7 @@ using leveldb::WriteOptions;
documentKey:mutation.key
batchID:batchID];
_db.currentTransaction->Delete(key);
+ [_db.referenceDelegate removeMutationReference:mutation.key];
[garbageCollector addPotentialGarbageKey:mutation.key];
}
}
diff --git a/Firestore/Source/Local/FSTLevelDBQueryCache.mm b/Firestore/Source/Local/FSTLevelDBQueryCache.mm
index 5fde7d7..68b6f98 100644
--- a/Firestore/Source/Local/FSTLevelDBQueryCache.mm
+++ b/Firestore/Source/Local/FSTLevelDBQueryCache.mm
@@ -18,6 +18,7 @@
#include <memory>
#include <string>
+#include <utility>
#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
#import "Firestore/Source/Core/FSTQuery.h"
@@ -28,6 +29,7 @@
#import "Firestore/Source/Util/FSTAssert.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
#include "absl/strings/match.h"
NS_ASSUME_NONNULL_BEGIN
@@ -35,9 +37,11 @@ NS_ASSUME_NONNULL_BEGIN
using firebase::firestore::local::LevelDbTransaction;
using Firestore::StringView;
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::SnapshotVersion;
using leveldb::DB;
using leveldb::Slice;
using leveldb::Status;
+using firebase::firestore::model::DocumentKeySet;
@interface FSTLevelDBQueryCache ()
@@ -55,7 +59,7 @@ using leveldb::Status;
* The last received snapshot version. This is part of `metadata` but we store it separately to
* avoid extra conversion to/from GPBTimestamp.
*/
- FSTSnapshotVersion *_lastRemoteSnapshotVersion;
+ SnapshotVersion _lastRemoteSnapshotVersion;
}
+ (nullable FSTPBTargetGlobal *)readTargetMetadataWithTransaction:
@@ -135,13 +139,14 @@ using leveldb::Status;
return self.metadata.highestListenSequenceNumber;
}
-- (FSTSnapshotVersion *)lastRemoteSnapshotVersion {
+- (const SnapshotVersion &)lastRemoteSnapshotVersion {
return _lastRemoteSnapshotVersion;
}
-- (void)setLastRemoteSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion {
- _lastRemoteSnapshotVersion = snapshotVersion;
- self.metadata.lastRemoteSnapshotVersion = [self.serializer encodedVersion:snapshotVersion];
+- (void)setLastRemoteSnapshotVersion:(SnapshotVersion)snapshotVersion {
+ _lastRemoteSnapshotVersion = std::move(snapshotVersion);
+ self.metadata.lastRemoteSnapshotVersion =
+ [self.serializer encodedVersion:_lastRemoteSnapshotVersion];
_db.currentTransaction->Put([FSTLevelDBTargetGlobalKey key], self.metadata);
}
@@ -278,30 +283,30 @@ using leveldb::Status;
#pragma mark Matching Key tracking
-- (void)addMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID {
+- (void)addMatchingKeys:(const DocumentKeySet &)keys forTargetID:(FSTTargetID)targetID {
// Store an empty value in the index which is equivalent to serializing a GPBEmpty message. In the
// future if we wanted to store some other kind of value here, we can parse these empty values as
// with some other protocol buffer (and the parser will see all default values).
std::string emptyBuffer;
- [keys enumerateObjectsUsingBlock:^(FSTDocumentKey *documentKey, BOOL *stop) {
+ for (const DocumentKey &key : keys) {
self->_db.currentTransaction->Put(
- [FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:documentKey],
- emptyBuffer);
+ [FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key], emptyBuffer);
self->_db.currentTransaction->Put(
- [FSTLevelDBDocumentTargetKey keyWithDocumentKey:documentKey targetID:targetID],
- emptyBuffer);
- }];
+ [FSTLevelDBDocumentTargetKey keyWithDocumentKey:key targetID:targetID], emptyBuffer);
+ [self->_db.referenceDelegate addReference:key target:targetID];
+ };
}
-- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID {
- [keys enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
+- (void)removeMatchingKeys:(const DocumentKeySet &)keys forTargetID:(FSTTargetID)targetID {
+ for (const DocumentKey &key : keys) {
self->_db.currentTransaction->Delete(
[FSTLevelDBTargetDocumentKey keyWithTargetID:targetID documentKey:key]);
self->_db.currentTransaction->Delete(
[FSTLevelDBDocumentTargetKey keyWithDocumentKey:key targetID:targetID]);
+ [self->_db.referenceDelegate removeReference:key target:targetID];
[self.garbageCollector addPotentialGarbageKey:key];
- }];
+ }
}
- (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID {
@@ -327,12 +332,12 @@ using leveldb::Status;
}
}
-- (FSTDocumentKeySet *)matchingKeysForTargetID:(FSTTargetID)targetID {
+- (DocumentKeySet)matchingKeysForTargetID:(FSTTargetID)targetID {
std::string indexPrefix = [FSTLevelDBTargetDocumentKey keyPrefixWithTargetID:targetID];
auto indexIterator = _db.currentTransaction->NewIterator();
indexIterator->Seek(indexPrefix);
- FSTDocumentKeySet *result = [FSTDocumentKeySet keySet];
+ DocumentKeySet result;
FSTLevelDBTargetDocumentKey *rowKey = [[FSTLevelDBTargetDocumentKey alloc] init];
for (; indexIterator->Valid(); indexIterator->Next()) {
absl::string_view indexKey = indexIterator->key();
@@ -342,7 +347,7 @@ using leveldb::Status;
break;
}
- result = [result setByAddingObject:rowKey.documentKey];
+ result = result.insert(rowKey.documentKey);
}
return result;
diff --git a/Firestore/Source/Local/FSTLocalDocumentsView.h b/Firestore/Source/Local/FSTLocalDocumentsView.h
index e75e0f3..bb5bb22 100644
--- a/Firestore/Source/Local/FSTLocalDocumentsView.h
+++ b/Firestore/Source/Local/FSTLocalDocumentsView.h
@@ -17,9 +17,9 @@
#import <Foundation/Foundation.h>
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
-#import "Firestore/Source/Model/FSTDocumentKeySet.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
@class FSTMaybeDocument;
@class FSTQuery;
@@ -53,7 +53,8 @@ NS_ASSUME_NONNULL_BEGIN
* If we don't have cached state for a document in `keys`, a FSTDeletedDocument will be stored
* for that key in the resulting set.
*/
-- (FSTMaybeDocumentDictionary *)documentsForKeys:(FSTDocumentKeySet *)keys;
+- (FSTMaybeDocumentDictionary *)documentsForKeys:
+ (const firebase::firestore::model::DocumentKeySet &)keys;
/** Performs a query against the local view of all documents. */
- (FSTDocumentDictionary *)documentsMatchingQuery:(FSTQuery *)query;
diff --git a/Firestore/Source/Local/FSTLocalDocumentsView.mm b/Firestore/Source/Local/FSTLocalDocumentsView.mm
index e9b9423..471840a 100644
--- a/Firestore/Source/Local/FSTLocalDocumentsView.mm
+++ b/Firestore/Source/Local/FSTLocalDocumentsView.mm
@@ -17,7 +17,6 @@
#import "Firestore/Source/Local/FSTLocalDocumentsView.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Local/FSTMutationQueue.h"
#import "Firestore/Source/Local/FSTRemoteDocumentCache.h"
#import "Firestore/Source/Model/FSTDocument.h"
@@ -28,9 +27,12 @@
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::ResourcePath;
+using firebase::firestore::model::SnapshotVersion;
+using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
@@ -64,14 +66,14 @@ NS_ASSUME_NONNULL_BEGIN
return [self localDocument:remoteDoc key:key];
}
-- (FSTMaybeDocumentDictionary *)documentsForKeys:(FSTDocumentKeySet *)keys {
+- (FSTMaybeDocumentDictionary *)documentsForKeys:(const DocumentKeySet &)keys {
FSTMaybeDocumentDictionary *results = [FSTMaybeDocumentDictionary maybeDocumentDictionary];
- for (FSTDocumentKey *key in keys.objectEnumerator) {
+ for (const DocumentKey &key : keys) {
// TODO(mikelehen): PERF: Consider fetching all remote documents at once rather than one-by-one.
FSTMaybeDocument *maybeDoc = [self documentForKey:key];
// TODO(http://b/32275378): Don't conflate missing / deleted.
if (!maybeDoc) {
- maybeDoc = [FSTDeletedDocument documentWithKey:key version:[FSTSnapshotVersion noVersion]];
+ maybeDoc = [FSTDeletedDocument documentWithKey:key version:SnapshotVersion::None()];
}
results = [results dictionaryBySettingObject:maybeDoc forKey:key];
}
@@ -105,7 +107,7 @@ NS_ASSUME_NONNULL_BEGIN
// Now use the mutation queue to discover any other documents that may match the query after
// applying mutations.
- FSTDocumentKeySet *matchingKeys = [FSTDocumentKeySet keySet];
+ DocumentKeySet matchingKeys;
NSArray<FSTMutationBatch *> *matchingMutationBatches =
[self.mutationQueue allMutationBatchesAffectingQuery:query];
for (FSTMutationBatch *batch in matchingMutationBatches) {
@@ -114,13 +116,13 @@ NS_ASSUME_NONNULL_BEGIN
// If the key is already in the results, we can skip it.
if (![results containsKey:mutation.key]) {
- matchingKeys = [matchingKeys setByAddingObject:mutation.key];
+ matchingKeys = matchingKeys.insert(mutation.key);
}
}
}
// Now add in results for the matchingKeys.
- for (FSTDocumentKey *key in matchingKeys.objectEnumerator) {
+ for (const DocumentKey &key : matchingKeys) {
FSTMaybeDocument *doc = [self documentForKey:key];
if ([doc isKindOfClass:[FSTDocument class]]) {
results = [results dictionaryBySettingObject:(FSTDocument *)doc forKey:key];
diff --git a/Firestore/Source/Local/FSTLocalSerializer.h b/Firestore/Source/Local/FSTLocalSerializer.h
index 6ca7f01..b75f3e6 100644
--- a/Firestore/Source/Local/FSTLocalSerializer.h
+++ b/Firestore/Source/Local/FSTLocalSerializer.h
@@ -16,11 +16,12 @@
#import <Foundation/Foundation.h>
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+
@class FSTMaybeDocument;
@class FSTMutationBatch;
@class FSTQueryData;
@class FSTSerializerBeta;
-@class FSTSnapshotVersion;
@class FSTPBMaybeDocument;
@class FSTPBTarget;
@@ -61,11 +62,11 @@ NS_ASSUME_NONNULL_BEGIN
/** Decodes an FSTPBTarget proto from local storage into an FSTQueryData model. */
- (FSTQueryData *)decodedQueryData:(FSTPBTarget *)target;
-/** Encodes an FSTSnapshotVersion model into a GPBTimestamp proto. */
-- (GPBTimestamp *)encodedVersion:(FSTSnapshotVersion *)version;
+/** Encodes a SnapshotVersion model into a GPBTimestamp proto. */
+- (GPBTimestamp *)encodedVersion:(const firebase::firestore::model::SnapshotVersion &)version;
-/** Decodes a GPBTimestamp proto into a FSTSnapshotVersion model. */
-- (FSTSnapshotVersion *)decodedVersion:(GPBTimestamp *)version;
+/** Decodes a GPBTimestamp proto into a SnapshotVersion model. */
+- (firebase::firestore::model::SnapshotVersion)decodedVersion:(GPBTimestamp *)version;
@end
diff --git a/Firestore/Source/Local/FSTLocalSerializer.mm b/Firestore/Source/Local/FSTLocalSerializer.mm
index 61e173a..2c5ab4d 100644
--- a/Firestore/Source/Local/FSTLocalSerializer.mm
+++ b/Firestore/Source/Local/FSTLocalSerializer.mm
@@ -18,6 +18,7 @@
#include <cinttypes>
+#import "FIRTimestamp.h"
#import "Firestore/Protos/objc/firestore/local/MaybeDocument.pbobjc.h"
#import "Firestore/Protos/objc/firestore/local/Mutation.pbobjc.h"
#import "Firestore/Protos/objc/firestore/local/Target.pbobjc.h"
@@ -30,9 +31,13 @@
#import "Firestore/Source/Remote/FSTSerializerBeta.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+using firebase::Timestamp;
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::SnapshotVersion;
@interface FSTLocalSerializer ()
@@ -99,8 +104,8 @@ using firebase::firestore::model::DocumentKey;
FSTSerializerBeta *remoteSerializer = self.remoteSerializer;
FSTObjectValue *data = [remoteSerializer decodedFields:document.fields];
- const DocumentKey key = [remoteSerializer decodedDocumentKey:document.name];
- FSTSnapshotVersion *version = [remoteSerializer decodedVersion:document.updateTime];
+ DocumentKey key = [remoteSerializer decodedDocumentKey:document.name];
+ SnapshotVersion version = [remoteSerializer decodedVersion:document.updateTime];
return [FSTDocument documentWithData:data key:key version:version hasLocalMutations:NO];
}
@@ -118,8 +123,8 @@ using firebase::firestore::model::DocumentKey;
- (FSTDeletedDocument *)decodedDeletedDocument:(FSTPBNoDocument *)proto {
FSTSerializerBeta *remoteSerializer = self.remoteSerializer;
- const DocumentKey key = [remoteSerializer decodedDocumentKey:proto.name];
- FSTSnapshotVersion *version = [remoteSerializer decodedVersion:proto.readTime];
+ DocumentKey key = [remoteSerializer decodedDocumentKey:proto.name];
+ SnapshotVersion version = [remoteSerializer decodedVersion:proto.readTime];
return [FSTDeletedDocument documentWithKey:key version:version];
}
@@ -128,7 +133,8 @@ using firebase::firestore::model::DocumentKey;
FSTPBWriteBatch *proto = [FSTPBWriteBatch message];
proto.batchId = batch.batchID;
- proto.localWriteTime = [remoteSerializer encodedTimestamp:batch.localWriteTime];
+ proto.localWriteTime = [remoteSerializer
+ encodedTimestamp:Timestamp{batch.localWriteTime.seconds, batch.localWriteTime.nanoseconds}];
NSMutableArray<GCFSWrite *> *writes = proto.writesArray;
for (FSTMutation *mutation in batch.mutations) {
@@ -146,11 +152,13 @@ using firebase::firestore::model::DocumentKey;
[mutations addObject:[remoteSerializer decodedMutation:write]];
}
- FIRTimestamp *localWriteTime = [remoteSerializer decodedTimestamp:batch.localWriteTime];
+ Timestamp localWriteTime = [remoteSerializer decodedTimestamp:batch.localWriteTime];
- return [[FSTMutationBatch alloc] initWithBatchID:batchID
- localWriteTime:localWriteTime
- mutations:mutations];
+ return [[FSTMutationBatch alloc]
+ initWithBatchID:batchID
+ localWriteTime:[FIRTimestamp timestampWithSeconds:localWriteTime.seconds()
+ nanoseconds:localWriteTime.nanoseconds()]
+ mutations:mutations];
}
- (FSTPBTarget *)encodedQueryData:(FSTQueryData *)queryData {
@@ -181,7 +189,7 @@ using firebase::firestore::model::DocumentKey;
FSTTargetID targetID = target.targetId;
FSTListenSequenceNumber sequenceNumber = target.lastListenSequenceNumber;
- FSTSnapshotVersion *version = [remoteSerializer decodedVersion:target.snapshotVersion];
+ SnapshotVersion version = [remoteSerializer decodedVersion:target.snapshotVersion];
NSData *resumeToken = target.resumeToken;
FSTQuery *query;
@@ -206,11 +214,11 @@ using firebase::firestore::model::DocumentKey;
resumeToken:resumeToken];
}
-- (GPBTimestamp *)encodedVersion:(FSTSnapshotVersion *)version {
+- (GPBTimestamp *)encodedVersion:(const SnapshotVersion &)version {
return [self.remoteSerializer encodedVersion:version];
}
-- (FSTSnapshotVersion *)decodedVersion:(GPBTimestamp *)version {
+- (SnapshotVersion)decodedVersion:(GPBTimestamp *)version {
return [self.remoteSerializer decodedVersion:version];
}
diff --git a/Firestore/Source/Local/FSTLocalStore.h b/Firestore/Source/Local/FSTLocalStore.h
index 82402e4..1f4146a 100644
--- a/Firestore/Source/Local/FSTLocalStore.h
+++ b/Firestore/Source/Local/FSTLocalStore.h
@@ -18,11 +18,11 @@
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
-#import "Firestore/Source/Model/FSTDocumentKeySet.h"
-#import "Firestore/Source/Model/FSTDocumentVersionDictionary.h"
#include "Firestore/core/src/firebase/firestore/auth/user.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
@class FSTLocalViewChanges;
@class FSTLocalWriteResult;
@@ -141,7 +141,7 @@ NS_ASSUME_NONNULL_BEGIN
* Returns the last consistent snapshot processed (used by the RemoteStore to determine whether to
* buffer incoming snapshots from the backend).
*/
-- (FSTSnapshotVersion *)lastRemoteSnapshotVersion;
+- (const firebase::firestore::model::SnapshotVersion &)lastRemoteSnapshotVersion;
/**
* Updates the "ground-state" (remote) documents. We assume that the remote event reflects any
@@ -156,7 +156,7 @@ NS_ASSUME_NONNULL_BEGIN
* Returns the keys of the documents that are associated with the given targetID in the remote
* table.
*/
-- (FSTDocumentKeySet *)remoteDocumentKeysForTarget:(FSTTargetID)targetID;
+- (firebase::firestore::model::DocumentKeySet)remoteDocumentKeysForTarget:(FSTTargetID)targetID;
/**
* Collects garbage if necessary.
diff --git a/Firestore/Source/Local/FSTLocalStore.mm b/Firestore/Source/Local/FSTLocalStore.mm
index b5dfeec..0d6a785 100644
--- a/Firestore/Source/Local/FSTLocalStore.mm
+++ b/Firestore/Source/Local/FSTLocalStore.mm
@@ -21,7 +21,6 @@
#import "FIRTimestamp.h"
#import "Firestore/Source/Core/FSTListenSequence.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Local/FSTGarbageCollector.h"
#import "Firestore/Source/Local/FSTLocalDocumentsView.h"
#import "Firestore/Source/Local/FSTLocalViewChanges.h"
@@ -43,11 +42,14 @@
#include "Firestore/core/src/firebase/firestore/auth/user.h"
#include "Firestore/core/src/firebase/firestore/core/target_id_generator.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
using firebase::firestore::auth::User;
-using firebase::firestore::model::DocumentKey;
using firebase::firestore::core::TargetIdGenerator;
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::SnapshotVersion;
+using firebase::firestore::model::DocumentKeySet;
+using firebase::firestore::model::DocumentVersionMap;
NS_ASSUME_NONNULL_BEGIN
@@ -112,6 +114,7 @@ NS_ASSUME_NONNULL_BEGIN
_localDocuments = [FSTLocalDocumentsView viewWithRemoteDocumentCache:_remoteDocumentCache
mutationQueue:_mutationQueue];
_localViewReferences = [[FSTReferenceSet alloc] init];
+ [_persistence.referenceDelegate addInMemoryPins:_localViewReferences];
_garbageCollector = garbageCollector;
[_garbageCollector addGarbageSource:_queryCache];
@@ -186,11 +189,11 @@ NS_ASSUME_NONNULL_BEGIN
mutationQueue:self.mutationQueue];
// Union the old/new changed keys.
- FSTDocumentKeySet *changedKeys = [FSTDocumentKeySet keySet];
+ DocumentKeySet changedKeys;
for (NSArray<FSTMutationBatch *> *batches in @[ oldBatches, newBatches ]) {
for (FSTMutationBatch *batch in batches) {
for (FSTMutation *mutation in batch.mutations) {
- changedKeys = [changedKeys setByAddingObject:mutation.key];
+ changedKeys = changedKeys.insert(mutation.key);
}
}
}
@@ -205,7 +208,7 @@ NS_ASSUME_NONNULL_BEGIN
FIRTimestamp *localWriteTime = [FIRTimestamp timestamp];
FSTMutationBatch *batch =
[self.mutationQueue addMutationBatchWithWriteTime:localWriteTime mutations:mutations];
- FSTDocumentKeySet *keys = [batch keys];
+ DocumentKeySet keys = [batch keys];
FSTMaybeDocumentDictionary *changedDocuments = [self.localDocuments documentsForKeys:keys];
return [FSTLocalWriteResult resultForBatchID:batch.batchID changes:changedDocuments];
});
@@ -217,10 +220,9 @@ NS_ASSUME_NONNULL_BEGIN
[mutationQueue acknowledgeBatch:batchResult.batch streamToken:batchResult.streamToken];
- FSTDocumentKeySet *affected;
+ DocumentKeySet affected;
if ([self shouldHoldBatchResultWithVersion:batchResult.commitVersion]) {
[self.heldBatchResults addObject:batchResult];
- affected = [FSTDocumentKeySet keySet];
} else {
affected = [self releaseBatchResults:@[ batchResult ]];
}
@@ -239,7 +241,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTBatchID lastAcked = [self.mutationQueue highestAcknowledgedBatchID];
FSTAssert(batchID > lastAcked, @"Acknowledged batches can't be rejected.");
- FSTDocumentKeySet *affected = [self removeMutationBatch:toReject];
+ DocumentKeySet affected = [self removeMutationBatch:toReject];
[self.mutationQueue performConsistencyCheck];
@@ -256,12 +258,14 @@ NS_ASSUME_NONNULL_BEGIN
[&]() { [self.mutationQueue setLastStreamToken:streamToken]; });
}
-- (FSTSnapshotVersion *)lastRemoteSnapshotVersion {
+- (const SnapshotVersion &)lastRemoteSnapshotVersion {
return [self.queryCache lastRemoteSnapshotVersion];
}
- (FSTMaybeDocumentDictionary *)applyRemoteEvent:(FSTRemoteEvent *)remoteEvent {
return self.persistence.run("Apply remote event", [&]() -> FSTMaybeDocumentDictionary * {
+ // TODO(gsoltis): move the sequence number into the reference delegate.
+ FSTListenSequenceNumber sequenceNumber = [self.listenSequence next];
id<FSTQueryCache> queryCache = self.queryCache;
[remoteEvent.targetChanges enumerateKeysAndObjectsUsingBlock:^(
@@ -274,6 +278,18 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
+ // Update the resume token if the change includes one. Don't clear any preexisting value.
+ // Bump the sequence number as well, so that documents being removed now are ordered later
+ // than documents that were previously removed from this target.
+ NSData *resumeToken = change.resumeToken;
+ if (resumeToken.length > 0) {
+ queryData = [queryData queryDataByReplacingSnapshotVersion:change.snapshotVersion
+ resumeToken:resumeToken
+ sequenceNumber:sequenceNumber];
+ self.targetIDs[targetIDNumber] = queryData;
+ [self.queryCache updateQueryData:queryData];
+ }
+
FSTTargetMapping *mapping = change.mapping;
if (mapping) {
// First make sure that all references are deleted.
@@ -291,61 +307,58 @@ NS_ASSUME_NONNULL_BEGIN
FSTFail(@"Unknown mapping type: %@", mapping);
}
}
-
- // Update the resume token if the change includes one. Don't clear any preexisting value.
- NSData *resumeToken = change.resumeToken;
- if (resumeToken.length > 0) {
- queryData = [queryData queryDataByReplacingSnapshotVersion:change.snapshotVersion
- resumeToken:resumeToken];
- self.targetIDs[targetIDNumber] = queryData;
- [self.queryCache updateQueryData:queryData];
- }
}];
// TODO(klimt): This could probably be an NSMutableDictionary.
- FSTDocumentKeySet *changedDocKeys = [FSTDocumentKeySet keySet];
+ DocumentKeySet changedDocKeys;
+ const DocumentKeySet &limboDocuments = remoteEvent.limboDocumentChanges;
for (const auto &kv : remoteEvent.documentUpdates) {
const DocumentKey &key = kv.first;
FSTMaybeDocument *doc = kv.second;
- changedDocKeys = [changedDocKeys setByAddingObject:key];
+ changedDocKeys = changedDocKeys.insert(key);
FSTMaybeDocument *existingDoc = [self.remoteDocumentCache entryForKey:key];
// Make sure we don't apply an old document version to the remote cache, though we
- // make an exception for [SnapshotVersion noVersion] which can happen for manufactured
+ // make an exception for SnapshotVersion::None() which can happen for manufactured
// events (e.g. in the case of a limbo document resolution failing).
- if (!existingDoc || [doc.version isEqual:[FSTSnapshotVersion noVersion]] ||
- [doc.version compare:existingDoc.version] != NSOrderedAscending) {
+ if (!existingDoc || SnapshotVersion{doc.version} == SnapshotVersion::None() ||
+ SnapshotVersion{doc.version} >= SnapshotVersion{existingDoc.version}) {
[self.remoteDocumentCache addEntry:doc];
} else {
FSTLog(
@"FSTLocalStore Ignoring outdated watch update for %s. "
- "Current version: %@ Watch version: %@",
- key.ToString().c_str(), existingDoc.version, doc.version);
+ "Current version: %s Watch version: %s",
+ key.ToString().c_str(), existingDoc.version.timestamp().ToString().c_str(),
+ doc.version.timestamp().ToString().c_str());
}
// The document might be garbage because it was unreferenced by everything.
// Make sure to mark it as garbage if it is...
[self.garbageCollector addPotentialGarbageKey:key];
+ if (limboDocuments.contains(key)) {
+ [self.persistence.referenceDelegate limboDocumentUpdated:key];
+ }
}
// HACK: The only reason we allow omitting snapshot version is so we can synthesize remote
// events when we get permission denied errors while trying to resolve the state of a locally
// cached document that is in limbo.
- FSTSnapshotVersion *lastRemoteVersion = [self.queryCache lastRemoteSnapshotVersion];
- FSTSnapshotVersion *remoteVersion = remoteEvent.snapshotVersion;
- if (![remoteVersion isEqual:[FSTSnapshotVersion noVersion]]) {
- FSTAssert([remoteVersion compare:lastRemoteVersion] != NSOrderedAscending,
- @"Watch stream reverted to previous snapshot?? (%@ < %@)", remoteVersion,
- lastRemoteVersion);
+ const SnapshotVersion &lastRemoteVersion = [self.queryCache lastRemoteSnapshotVersion];
+ const SnapshotVersion &remoteVersion = remoteEvent.snapshotVersion;
+ if (remoteVersion != SnapshotVersion::None()) {
+ FSTAssert(remoteVersion >= lastRemoteVersion,
+ @"Watch stream reverted to previous snapshot?? (%s < %s)",
+ remoteVersion.timestamp().ToString().c_str(),
+ lastRemoteVersion.timestamp().ToString().c_str());
[self.queryCache setLastRemoteSnapshotVersion:remoteVersion];
}
- FSTDocumentKeySet *releasedWriteKeys = [self releaseHeldBatchResults];
+ DocumentKeySet releasedWriteKeys = [self releaseHeldBatchResults];
// Union the two key sets.
- __block FSTDocumentKeySet *keysToRecalc = changedDocKeys;
- [releasedWriteKeys enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
- keysToRecalc = [keysToRecalc setByAddingObject:key];
- }];
+ DocumentKeySet keysToRecalc = changedDocKeys;
+ for (const DocumentKey &key : releasedWriteKeys) {
+ keysToRecalc = keysToRecalc.insert(key);
+ }
return [self.localDocuments documentsForKeys:keysToRecalc];
});
@@ -358,6 +371,9 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryData *queryData = [self.queryCache queryDataForQuery:view.query];
FSTAssert(queryData, @"Local view changes contain unallocated query.");
FSTTargetID targetID = queryData.targetID;
+ for (const DocumentKey &key : view.removedKeys) {
+ [self->_persistence.referenceDelegate removeReference:key target:targetID];
+ }
[localViewReferences addReferencesToKeys:view.addedKeys forID:targetID];
[localViewReferences removeReferencesToKeys:view.removedKeys forID:targetID];
}
@@ -408,6 +424,7 @@ NS_ASSUME_NONNULL_BEGIN
if (self.garbageCollector.isEager) {
[self.queryCache removeQueryData:queryData];
}
+ [self.persistence.referenceDelegate removeTarget:queryData];
[self.targetIDs removeObjectForKey:@(queryData.targetID)];
// If this was the last watch target, then we won't get any more watch snapshots, so we should
@@ -424,8 +441,8 @@ NS_ASSUME_NONNULL_BEGIN
});
}
-- (FSTDocumentKeySet *)remoteDocumentKeysForTarget:(FSTTargetID)targetID {
- return self.persistence.run("RemoteDocumentKeysForTarget", [&]() -> FSTDocumentKeySet * {
+- (DocumentKeySet)remoteDocumentKeysForTarget:(FSTTargetID)targetID {
+ return self.persistence.run("RemoteDocumentKeysForTarget", [&]() -> DocumentKeySet {
return [self.queryCache matchingKeysForTargetID:targetID];
});
}
@@ -449,7 +466,7 @@ NS_ASSUME_NONNULL_BEGIN
*
* @return the set of keys of docs that were modified by those writes.
*/
-- (FSTDocumentKeySet *)releaseHeldBatchResults {
+- (DocumentKeySet)releaseHeldBatchResults {
NSMutableArray<FSTMutationBatchResult *> *toRelease = [NSMutableArray array];
for (FSTMutationBatchResult *batchResult in self.heldBatchResults) {
if (![self isRemoteUpToVersion:batchResult.commitVersion]) {
@@ -459,25 +476,24 @@ NS_ASSUME_NONNULL_BEGIN
}
if (toRelease.count == 0) {
- return [FSTDocumentKeySet keySet];
+ return DocumentKeySet{};
} else {
[self.heldBatchResults removeObjectsInRange:NSMakeRange(0, toRelease.count)];
return [self releaseBatchResults:toRelease];
}
}
-- (BOOL)isRemoteUpToVersion:(FSTSnapshotVersion *)version {
+- (BOOL)isRemoteUpToVersion:(const SnapshotVersion &)version {
// If there are no watch targets, then we won't get remote snapshots, and are always "up-to-date."
- return [version compare:self.queryCache.lastRemoteSnapshotVersion] != NSOrderedDescending ||
- self.targetIDs.count == 0;
+ return version <= self.queryCache.lastRemoteSnapshotVersion || self.targetIDs.count == 0;
}
-- (BOOL)shouldHoldBatchResultWithVersion:(FSTSnapshotVersion *)version {
+- (BOOL)shouldHoldBatchResultWithVersion:(const SnapshotVersion &)version {
// Check if watcher isn't up to date or prior results are already held.
return ![self isRemoteUpToVersion:version] || self.heldBatchResults.count > 0;
}
-- (FSTDocumentKeySet *)releaseBatchResults:(NSArray<FSTMutationBatchResult *> *)batchResults {
+- (DocumentKeySet)releaseBatchResults:(NSArray<FSTMutationBatchResult *> *)batchResults {
NSMutableArray<FSTMutationBatch *> *batches = [NSMutableArray array];
for (FSTMutationBatchResult *batchResult in batchResults) {
[self applyBatchResult:batchResult];
@@ -487,36 +503,37 @@ NS_ASSUME_NONNULL_BEGIN
return [self removeMutationBatches:batches];
}
-- (FSTDocumentKeySet *)removeMutationBatch:(FSTMutationBatch *)batch {
+- (DocumentKeySet)removeMutationBatch:(FSTMutationBatch *)batch {
return [self removeMutationBatches:@[ batch ]];
}
/** Removes all the mutation batches named in the given array. */
-- (FSTDocumentKeySet *)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches {
- // TODO(klimt): Could this be an NSMutableDictionary?
- __block FSTDocumentKeySet *affectedDocs = [FSTDocumentKeySet keySet];
-
+- (DocumentKeySet)removeMutationBatches:(NSArray<FSTMutationBatch *> *)batches {
+ DocumentKeySet affectedDocs;
for (FSTMutationBatch *batch in batches) {
for (FSTMutation *mutation in batch.mutations) {
const DocumentKey &key = mutation.key;
- affectedDocs = [affectedDocs setByAddingObject:key];
+ affectedDocs = affectedDocs.insert(key);
}
}
[self.mutationQueue removeMutationBatches:batches];
-
return affectedDocs;
}
- (void)applyBatchResult:(FSTMutationBatchResult *)batchResult {
FSTMutationBatch *batch = batchResult.batch;
- FSTDocumentKeySet *docKeys = batch.keys;
- [docKeys enumerateObjectsUsingBlock:^(FSTDocumentKey *docKey, BOOL *stop) {
+ DocumentKeySet docKeys = batch.keys;
+ const DocumentVersionMap &versions = batchResult.docVersions;
+ for (const DocumentKey &docKey : docKeys) {
FSTMaybeDocument *_Nullable remoteDoc = [self.remoteDocumentCache entryForKey:docKey];
FSTMaybeDocument *_Nullable doc = remoteDoc;
- FSTSnapshotVersion *ackVersion = batchResult.docVersions[docKey];
- FSTAssert(ackVersion, @"docVersions should contain every doc in the write.");
- if (!doc || [doc.version compare:ackVersion] == NSOrderedAscending) {
+
+ auto ackVersionIter = versions.find(docKey);
+ FSTAssert(ackVersionIter != versions.end(),
+ @"docVersions should contain every doc in the write.");
+ const SnapshotVersion &ackVersion = ackVersionIter->second;
+ if (!doc || doc.version < ackVersion) {
doc = [batch applyTo:doc documentKey:docKey mutationBatchResult:batchResult];
if (!doc) {
FSTAssert(!remoteDoc, @"Mutation batch %@ applied to document %@ resulted in nil.", batch,
@@ -525,7 +542,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.remoteDocumentCache addEntry:doc];
}
}
- }];
+ }
}
@end
diff --git a/Firestore/Source/Local/FSTLocalViewChanges.h b/Firestore/Source/Local/FSTLocalViewChanges.h
index eb84642..143d010 100644
--- a/Firestore/Source/Local/FSTLocalViewChanges.h
+++ b/Firestore/Source/Local/FSTLocalViewChanges.h
@@ -16,7 +16,7 @@
#import <Foundation/Foundation.h>
-#import "Firestore/Source/Model/FSTDocumentKeySet.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
@class FSTDocumentSet;
@class FSTMutation;
@@ -34,16 +34,17 @@ NS_ASSUME_NONNULL_BEGIN
@interface FSTLocalViewChanges : NSObject
+ (instancetype)changesForQuery:(FSTQuery *)query
- addedKeys:(FSTDocumentKeySet *)addedKeys
- removedKeys:(FSTDocumentKeySet *)removedKeys;
+ addedKeys:(firebase::firestore::model::DocumentKeySet)addedKeys
+ removedKeys:(firebase::firestore::model::DocumentKeySet)removedKeys;
+ (instancetype)changesForViewSnapshot:(FSTViewSnapshot *)viewSnapshot;
- (id)init NS_UNAVAILABLE;
@property(nonatomic, strong, readonly) FSTQuery *query;
-@property(nonatomic, strong) FSTDocumentKeySet *addedKeys;
-@property(nonatomic, strong) FSTDocumentKeySet *removedKeys;
+
+- (const firebase::firestore::model::DocumentKeySet &)addedKeys;
+- (const firebase::firestore::model::DocumentKeySet &)removedKeys;
@end
diff --git a/Firestore/Source/Local/FSTLocalViewChanges.mm b/Firestore/Source/Local/FSTLocalViewChanges.mm
index 9a7f445..eb6b259 100644
--- a/Firestore/Source/Local/FSTLocalViewChanges.mm
+++ b/Firestore/Source/Local/FSTLocalViewChanges.mm
@@ -16,31 +16,38 @@
#import "Firestore/Source/Local/FSTLocalViewChanges.h"
+#include <utility>
+
#import "Firestore/Source/Core/FSTViewSnapshot.h"
#import "Firestore/Source/Model/FSTDocument.h"
+using firebase::firestore::model::DocumentKeySet;
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTLocalViewChanges ()
- (instancetype)initWithQuery:(FSTQuery *)query
- addedKeys:(FSTDocumentKeySet *)addedKeys
- removedKeys:(FSTDocumentKeySet *)removedKeys NS_DESIGNATED_INITIALIZER;
+ addedKeys:(DocumentKeySet)addedKeys
+ removedKeys:(DocumentKeySet)removedKeys NS_DESIGNATED_INITIALIZER;
@end
-@implementation FSTLocalViewChanges
+@implementation FSTLocalViewChanges {
+ DocumentKeySet _addedKeys;
+ DocumentKeySet _removedKeys;
+}
+ (instancetype)changesForViewSnapshot:(FSTViewSnapshot *)viewSnapshot {
- FSTDocumentKeySet *addedKeys = [FSTDocumentKeySet keySet];
- FSTDocumentKeySet *removedKeys = [FSTDocumentKeySet keySet];
+ DocumentKeySet addedKeys;
+ DocumentKeySet removedKeys;
for (FSTDocumentViewChange *docChange in viewSnapshot.documentChanges) {
switch (docChange.type) {
case FSTDocumentViewChangeTypeAdded:
- addedKeys = [addedKeys setByAddingObject:docChange.document.key];
+ addedKeys = addedKeys.insert(docChange.document.key);
break;
case FSTDocumentViewChangeTypeRemoved:
- removedKeys = [removedKeys setByAddingObject:docChange.document.key];
+ removedKeys = removedKeys.insert(docChange.document.key);
break;
default:
@@ -49,28 +56,39 @@ NS_ASSUME_NONNULL_BEGIN
}
}
- return [self changesForQuery:viewSnapshot.query addedKeys:addedKeys removedKeys:removedKeys];
+ return [self changesForQuery:viewSnapshot.query
+ addedKeys:std::move(addedKeys)
+ removedKeys:std::move(removedKeys)];
}
+ (instancetype)changesForQuery:(FSTQuery *)query
- addedKeys:(FSTDocumentKeySet *)addedKeys
- removedKeys:(FSTDocumentKeySet *)removedKeys {
- return
- [[FSTLocalViewChanges alloc] initWithQuery:query addedKeys:addedKeys removedKeys:removedKeys];
+ addedKeys:(DocumentKeySet)addedKeys
+ removedKeys:(DocumentKeySet)removedKeys {
+ return [[FSTLocalViewChanges alloc] initWithQuery:query
+ addedKeys:std::move(addedKeys)
+ removedKeys:std::move(removedKeys)];
}
- (instancetype)initWithQuery:(FSTQuery *)query
- addedKeys:(FSTDocumentKeySet *)addedKeys
- removedKeys:(FSTDocumentKeySet *)removedKeys {
+ addedKeys:(DocumentKeySet)addedKeys
+ removedKeys:(DocumentKeySet)removedKeys {
self = [super init];
if (self) {
_query = query;
- _addedKeys = addedKeys;
- _removedKeys = removedKeys;
+ _addedKeys = std::move(addedKeys);
+ _removedKeys = std::move(removedKeys);
}
return self;
}
+- (const DocumentKeySet &)addedKeys {
+ return _addedKeys;
+}
+
+- (const DocumentKeySet &)removedKeys {
+ return _removedKeys;
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTMemoryMutationQueue.h b/Firestore/Source/Local/FSTMemoryMutationQueue.h
index f0786cc..fd46a6e 100644
--- a/Firestore/Source/Local/FSTMemoryMutationQueue.h
+++ b/Firestore/Source/Local/FSTMemoryMutationQueue.h
@@ -16,6 +16,7 @@
#import <Foundation/Foundation.h>
+#import "Firestore/Source/Local/FSTMemoryPersistence.h"
#import "Firestore/Source/Local/FSTMutationQueue.h"
@protocol FSTGarbageCollector;
@@ -24,7 +25,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface FSTMemoryMutationQueue : NSObject <FSTMutationQueue>
-+ (instancetype)mutationQueue;
+- (instancetype)initWithPersistence:(FSTMemoryPersistence *)persistence NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
/** The garbage collector to notify about potential garbage keys. */
@property(nonatomic, weak, readwrite, nullable) id<FSTGarbageCollector> garbageCollector;
diff --git a/Firestore/Source/Local/FSTMemoryMutationQueue.mm b/Firestore/Source/Local/FSTMemoryMutationQueue.mm
index 8028bb3..e05ee64 100644
--- a/Firestore/Source/Local/FSTMemoryMutationQueue.mm
+++ b/Firestore/Source/Local/FSTMemoryMutationQueue.mm
@@ -18,9 +18,11 @@
#import "Firestore/Source/Core/FSTQuery.h"
#import "Firestore/Source/Local/FSTDocumentReference.h"
+#import "Firestore/Source/Local/FSTMemoryPersistence.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Model/FSTMutationBatch.h"
#import "Firestore/Source/Util/FSTAssert.h"
+#import "Firestore/third_party/Immutable/FSTImmutableSortedSet.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
@@ -72,14 +74,13 @@ static const NSComparator NumberComparator = ^NSComparisonResult(NSNumber *left,
@end
-@implementation FSTMemoryMutationQueue
-
-+ (instancetype)mutationQueue {
- return [[FSTMemoryMutationQueue alloc] init];
+@implementation FSTMemoryMutationQueue {
+ FSTMemoryPersistence *_persistence;
}
-- (instancetype)init {
+- (instancetype)initWithPersistence:(FSTMemoryPersistence *)persistence {
if (self = [super init]) {
+ _persistence = persistence;
_queue = [NSMutableArray array];
_batchesByDocumentKey =
[FSTImmutableSortedSet setWithComparator:FSTDocumentReferenceComparatorByKey];
@@ -347,6 +348,7 @@ static const NSComparator NumberComparator = ^NSComparisonResult(NSNumber *left,
for (FSTMutation *mutation in batch.mutations) {
const DocumentKey &key = mutation.key;
[garbageCollector addPotentialGarbageKey:key];
+ [_persistence.referenceDelegate removeMutationReference:key];
FSTDocumentReference *reference = [[FSTDocumentReference alloc] initWithKey:key ID:batchID];
references = [references setByRemovingObject:reference];
diff --git a/Firestore/Source/Local/FSTMemoryPersistence.mm b/Firestore/Source/Local/FSTMemoryPersistence.mm
index 8d74881..3466f3e 100644
--- a/Firestore/Source/Local/FSTMemoryPersistence.mm
+++ b/Firestore/Source/Local/FSTMemoryPersistence.mm
@@ -59,7 +59,7 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)init {
if (self = [super init]) {
- _queryCache = [[FSTMemoryQueryCache alloc] init];
+ _queryCache = [[FSTMemoryQueryCache alloc] initWithPersistence:self];
_remoteDocumentCache = [[FSTMemoryRemoteDocumentCache alloc] init];
}
return self;
@@ -78,6 +78,10 @@ NS_ASSUME_NONNULL_BEGIN
self.started = NO;
}
+- (_Nullable id<FSTReferenceDelegate>)referenceDelegate {
+ return nil;
+}
+
- (const FSTTransactionRunner &)run {
return _transactionRunner;
}
@@ -85,7 +89,7 @@ NS_ASSUME_NONNULL_BEGIN
- (id<FSTMutationQueue>)mutationQueueForUser:(const User &)user {
id<FSTMutationQueue> queue = _mutationQueues[user];
if (!queue) {
- queue = [FSTMemoryMutationQueue mutationQueue];
+ queue = [[FSTMemoryMutationQueue alloc] initWithPersistence:self];
_mutationQueues[user] = queue;
}
return queue;
diff --git a/Firestore/Source/Local/FSTMemoryQueryCache.h b/Firestore/Source/Local/FSTMemoryQueryCache.h
index 98f0277..126ce59 100644
--- a/Firestore/Source/Local/FSTMemoryQueryCache.h
+++ b/Firestore/Source/Local/FSTMemoryQueryCache.h
@@ -20,11 +20,18 @@
NS_ASSUME_NONNULL_BEGIN
+@class FSTMemoryPersistence;
+
/**
* An implementation of the FSTQueryCache protocol that merely keeps queries in memory, suitable
* for online only clients with persistence disabled.
*/
@interface FSTMemoryQueryCache : NSObject <FSTQueryCache>
+
+- (instancetype)initWithPersistence:(FSTMemoryPersistence *)persistence NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)init NS_UNAVAILABLE;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Local/FSTMemoryQueryCache.mm b/Firestore/Source/Local/FSTMemoryQueryCache.mm
index 18d14f2..2eba4f6 100644
--- a/Firestore/Source/Local/FSTMemoryQueryCache.mm
+++ b/Firestore/Source/Local/FSTMemoryQueryCache.mm
@@ -16,12 +16,19 @@
#import "Firestore/Source/Local/FSTMemoryQueryCache.h"
+#include <utility>
+
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
+#import "Firestore/Source/Local/FSTMemoryPersistence.h"
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Local/FSTReferenceSet.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+
+using firebase::firestore::model::SnapshotVersion;
+using firebase::firestore::model::DocumentKeySet;
+using firebase::firestore::model::DocumentKey;
NS_ASSUME_NONNULL_BEGIN
@@ -38,18 +45,20 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, assign) FSTListenSequenceNumber highestListenSequenceNumber;
-/** The last received snapshot version. */
-@property(nonatomic, strong) FSTSnapshotVersion *lastRemoteSnapshotVersion;
-
@end
-@implementation FSTMemoryQueryCache
+@implementation FSTMemoryQueryCache {
+ FSTMemoryPersistence *_persistence;
+ /** The last received snapshot version. */
+ SnapshotVersion _lastRemoteSnapshotVersion;
+}
-- (instancetype)init {
+- (instancetype)initWithPersistence:(FSTMemoryPersistence *)persistence {
if (self = [super init]) {
+ _persistence = persistence;
_queries = [NSMutableDictionary dictionary];
_references = [[FSTReferenceSet alloc] init];
- _lastRemoteSnapshotVersion = [FSTSnapshotVersion noVersion];
+ _lastRemoteSnapshotVersion = SnapshotVersion::None();
}
return self;
}
@@ -69,14 +78,13 @@ NS_ASSUME_NONNULL_BEGIN
return _highestListenSequenceNumber;
}
-/*- (FSTSnapshotVersion *)lastRemoteSnapshotVersion {
+- (const SnapshotVersion &)lastRemoteSnapshotVersion {
return _lastRemoteSnapshotVersion;
}
-- (void)setLastRemoteSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
- group:(FSTWriteGroup *)group {
- _lastRemoteSnapshotVersion = snapshotVersion;
-}*/
+- (void)setLastRemoteSnapshotVersion:(SnapshotVersion)snapshotVersion {
+ _lastRemoteSnapshotVersion = std::move(snapshotVersion);
+}
- (void)addQueryData:(FSTQueryData *)queryData {
self.queries[queryData.query] = queryData;
@@ -113,19 +121,25 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark Reference tracking
-- (void)addMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID {
+- (void)addMatchingKeys:(const DocumentKeySet &)keys forTargetID:(FSTTargetID)targetID {
[self.references addReferencesToKeys:keys forID:targetID];
+ for (const DocumentKey &key : keys) {
+ [_persistence.referenceDelegate addReference:key target:targetID];
+ }
}
-- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID {
+- (void)removeMatchingKeys:(const DocumentKeySet &)keys forTargetID:(FSTTargetID)targetID {
[self.references removeReferencesToKeys:keys forID:targetID];
+ for (const DocumentKey &key : keys) {
+ [_persistence.referenceDelegate removeReference:key target:targetID];
+ }
}
- (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID {
[self.references removeReferencesForID:targetID];
}
-- (FSTDocumentKeySet *)matchingKeysForTargetID:(FSTTargetID)targetID {
+- (DocumentKeySet)matchingKeysForTargetID:(FSTTargetID)targetID {
return [self.references referencedKeysForID:targetID];
}
diff --git a/Firestore/Source/Local/FSTPersistence.h b/Firestore/Source/Local/FSTPersistence.h
index 2294ef1..417ff3f 100644
--- a/Firestore/Source/Local/FSTPersistence.h
+++ b/Firestore/Source/Local/FSTPersistence.h
@@ -16,12 +16,16 @@
#import <Foundation/Foundation.h>
+#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Util/FSTAssert.h"
#include "Firestore/core/src/firebase/firestore/auth/user.h"
+@class FSTDocumentKey;
@protocol FSTMutationQueue;
@protocol FSTQueryCache;
+@class FSTQueryData;
@protocol FSTRemoteDocumentCache;
+@class FSTReferenceSet;
NS_ASSUME_NONNULL_BEGIN
@@ -56,6 +60,7 @@ NS_ASSUME_NONNULL_BEGIN
* of its reads and writes in order to avoid relying on being able to read back uncommitted writes.
*/
struct FSTTransactionRunner;
+@protocol FSTReferenceDelegate;
@protocol FSTPersistence <NSObject>
/**
@@ -87,6 +92,12 @@ struct FSTTransactionRunner;
@property(nonatomic, readonly, assign) const FSTTransactionRunner &run;
+/**
+ * This property provides access to hooks around the document reference lifecycle. It is initially
+ * nullable while being implemented, but the goal is to eventually have it be non-nil.
+ */
+@property(nonatomic, readonly, strong) _Nullable id<FSTReferenceDelegate> referenceDelegate;
+
@end
@protocol FSTTransactional
@@ -97,6 +108,52 @@ struct FSTTransactionRunner;
@end
+/**
+ * An FSTReferenceDelegate instance handles all of the hooks into the document-reference lifecycle.
+ * This includes being added to a target, being removed from a target, being subject to mutation,
+ * and being mutated by the user.
+ *
+ * Different implementations may do different things with each of these events. Not every
+ * implementation needs to do something with every lifecycle hook.
+ *
+ * Implementations that care about sequence numbers are responsible for generating them and making
+ * them available.
+ */
+@protocol FSTReferenceDelegate
+
+/**
+ * Registers an FSTReferenceSet of documents that should be considered 'referenced' and not eligible
+ * for removal during garbage collection.
+ */
+- (void)addInMemoryPins:(FSTReferenceSet *)set;
+
+/**
+ * Notify the delegate that a target was removed.
+ */
+- (void)removeTarget:(FSTQueryData *)queryData;
+
+/**
+ * Notify the delegate that the given document was added to the given target.
+ */
+- (void)addReference:(FSTDocumentKey *)key target:(FSTTargetID)targetID;
+
+/**
+ * Notify the delegate that the given document was removed from the given target.
+ */
+- (void)removeReference:(FSTDocumentKey *)key target:(FSTTargetID)targetID;
+
+/**
+ * Notify the delegate that a document is no longer being mutated by the user.
+ */
+- (void)removeMutationReference:(FSTDocumentKey *)key;
+
+/**
+ * Notify the delegate that a limbo document was updated.
+ */
+- (void)limboDocumentUpdated:(FSTDocumentKey *)key;
+
+@end
+
struct FSTTransactionRunner {
// Intentionally disable nullability checking for this function. We cannot properly annotate
// the function because this function can handle both pointer and non-pointer types. It is an error
diff --git a/Firestore/Source/Local/FSTQueryCache.h b/Firestore/Source/Local/FSTQueryCache.h
index d797d49..1ad46aa 100644
--- a/Firestore/Source/Local/FSTQueryCache.h
+++ b/Firestore/Source/Local/FSTQueryCache.h
@@ -18,13 +18,14 @@
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Local/FSTGarbageCollector.h"
-#import "Firestore/Source/Model/FSTDocumentKeySet.h"
+
+#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
@class FSTDocumentSet;
@class FSTMaybeDocument;
@class FSTQuery;
@class FSTQueryData;
-@class FSTSnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
@@ -61,7 +62,7 @@ NS_ASSUME_NONNULL_BEGIN
*
* This is updated whenever our we get a TargetChange with a read_time and empty target_ids.
*/
-- (FSTSnapshotVersion *)lastRemoteSnapshotVersion;
+- (const firebase::firestore::model::SnapshotVersion &)lastRemoteSnapshotVersion;
/**
* Set the snapshot version representing the last consistent snapshot received from the backend.
@@ -69,7 +70,7 @@ NS_ASSUME_NONNULL_BEGIN
*
* @param snapshotVersion The new snapshot version.
*/
-- (void)setLastRemoteSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion;
+- (void)setLastRemoteSnapshotVersion:(firebase::firestore::model::SnapshotVersion)snapshotVersion;
/**
* Adds an entry in the cache.
@@ -104,15 +105,17 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable FSTQueryData *)queryDataForQuery:(FSTQuery *)query;
/** Adds the given document keys to cached query results of the given target ID. */
-- (void)addMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID;
+- (void)addMatchingKeys:(const firebase::firestore::model::DocumentKeySet &)keys
+ forTargetID:(FSTTargetID)targetID;
/** Removes the given document keys from the cached query results of the given target ID. */
-- (void)removeMatchingKeys:(FSTDocumentKeySet *)keys forTargetID:(FSTTargetID)targetID;
+- (void)removeMatchingKeys:(const firebase::firestore::model::DocumentKeySet &)keys
+ forTargetID:(FSTTargetID)targetID;
/** Removes all the keys in the query results of the given target ID. */
- (void)removeMatchingKeysForTargetID:(FSTTargetID)targetID;
-- (FSTDocumentKeySet *)matchingKeysForTargetID:(FSTTargetID)targetID;
+- (firebase::firestore::model::DocumentKeySet)matchingKeysForTargetID:(FSTTargetID)targetID;
@end
diff --git a/Firestore/Source/Local/FSTQueryData.h b/Firestore/Source/Local/FSTQueryData.h
index 5db2de6..bde0a15 100644
--- a/Firestore/Source/Local/FSTQueryData.h
+++ b/Firestore/Source/Local/FSTQueryData.h
@@ -18,8 +18,9 @@
#import "Firestore/Source/Core/FSTTypes.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+
@class FSTQuery;
-@class FSTSnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
@@ -42,7 +43,7 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) {
targetID:(FSTTargetID)targetID
listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
purpose:(FSTQueryPurpose)purpose
- snapshotVersion:(FSTSnapshotVersion *)snapshotVersion
+ snapshotVersion:(firebase::firestore::model::SnapshotVersion)snapshotVersion
resumeToken:(NSData *)resumeToken NS_DESIGNATED_INITIALIZER;
/** Convenience initializer for use when creating an FSTQueryData for the first time. */
@@ -53,9 +54,17 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) {
- (instancetype)init NS_UNAVAILABLE;
-/** Creates a new query data instance with an updated snapshot version and resume token. */
-- (instancetype)queryDataByReplacingSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
- resumeToken:(NSData *)resumeToken;
+/**
+ * Creates a new query data instance with an updated snapshot version, resume token, and sequence
+ * number.
+ */
+- (instancetype)queryDataByReplacingSnapshotVersion:
+ (firebase::firestore::model::SnapshotVersion)snapshotVersion
+ resumeToken:(NSData *)resumeToken
+ sequenceNumber:(FSTListenSequenceNumber)sequenceNumber;
+
+/** The latest snapshot version seen for this target. */
+- (const firebase::firestore::model::SnapshotVersion &)snapshotVersion;
/** The query being listened to. */
@property(nonatomic, strong, readonly) FSTQuery *query;
@@ -71,9 +80,6 @@ typedef NS_ENUM(NSInteger, FSTQueryPurpose) {
/** The purpose of the query. */
@property(nonatomic, assign, readonly) FSTQueryPurpose purpose;
-/** The latest snapshot version seen for this target. */
-@property(nonatomic, strong, readonly) FSTSnapshotVersion *snapshotVersion;
-
/**
* An opaque, server-assigned token that allows watching a query to be resumed after disconnecting
* without retransmitting all the data that matches the query. The resume token essentially
diff --git a/Firestore/Source/Local/FSTQueryData.mm b/Firestore/Source/Local/FSTQueryData.mm
index 6bb716a..16c3b2e 100644
--- a/Firestore/Source/Local/FSTQueryData.mm
+++ b/Firestore/Source/Local/FSTQueryData.mm
@@ -16,18 +16,25 @@
#import "Firestore/Source/Local/FSTQueryData.h"
+#include <utility>
+
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
+
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+
+using firebase::firestore::model::SnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
-@implementation FSTQueryData
+@implementation FSTQueryData {
+ SnapshotVersion _snapshotVersion;
+}
- (instancetype)initWithQuery:(FSTQuery *)query
targetID:(FSTTargetID)targetID
listenSequenceNumber:(FSTListenSequenceNumber)sequenceNumber
purpose:(FSTQueryPurpose)purpose
- snapshotVersion:(FSTSnapshotVersion *)snapshotVersion
+ snapshotVersion:(SnapshotVersion)snapshotVersion
resumeToken:(NSData *)resumeToken {
self = [super init];
if (self) {
@@ -35,7 +42,7 @@ NS_ASSUME_NONNULL_BEGIN
_targetID = targetID;
_sequenceNumber = sequenceNumber;
_purpose = purpose;
- _snapshotVersion = snapshotVersion;
+ _snapshotVersion = std::move(snapshotVersion);
_resumeToken = [resumeToken copy];
}
return self;
@@ -49,10 +56,14 @@ NS_ASSUME_NONNULL_BEGIN
targetID:targetID
listenSequenceNumber:sequenceNumber
purpose:purpose
- snapshotVersion:[FSTSnapshotVersion noVersion]
+ snapshotVersion:SnapshotVersion::None()
resumeToken:[NSData data]];
}
+- (const firebase::firestore::model::SnapshotVersion &)snapshotVersion {
+ return _snapshotVersion;
+}
+
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
@@ -63,7 +74,7 @@ NS_ASSUME_NONNULL_BEGIN
FSTQueryData *other = (FSTQueryData *)object;
return [self.query isEqual:other.query] && self.targetID == other.targetID &&
- self.purpose == other.purpose && [self.snapshotVersion isEqual:other.snapshotVersion] &&
+ self.purpose == other.purpose && self.snapshotVersion == other.snapshotVersion &&
[self.resumeToken isEqual:other.resumeToken];
}
@@ -71,25 +82,26 @@ NS_ASSUME_NONNULL_BEGIN
NSUInteger result = [self.query hash];
result = result * 31 + self.targetID;
result = result * 31 + self.purpose;
- result = result * 31 + [self.snapshotVersion hash];
+ result = result * 31 + self.snapshotVersion.Hash();
result = result * 31 + [self.resumeToken hash];
return result;
}
- (NSString *)description {
return [NSString
- stringWithFormat:@"<FSTQueryData: query:%@ target:%d purpose:%lu version:%@ resumeToken:%@)>",
- self.query, self.targetID, (unsigned long)self.purpose, self.snapshotVersion,
- self.resumeToken];
+ stringWithFormat:@"<FSTQueryData: query:%@ target:%d purpose:%lu version:%s resumeToken:%@)>",
+ self.query, self.targetID, (unsigned long)self.purpose,
+ self.snapshotVersion.timestamp().ToString().c_str(), self.resumeToken];
}
-- (instancetype)queryDataByReplacingSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
- resumeToken:(NSData *)resumeToken {
+- (instancetype)queryDataByReplacingSnapshotVersion:(SnapshotVersion)snapshotVersion
+ resumeToken:(NSData *)resumeToken
+ sequenceNumber:(FSTListenSequenceNumber)sequenceNumber {
return [[FSTQueryData alloc] initWithQuery:self.query
targetID:self.targetID
- listenSequenceNumber:self.sequenceNumber
+ listenSequenceNumber:sequenceNumber
purpose:self.purpose
- snapshotVersion:snapshotVersion
+ snapshotVersion:std::move(snapshotVersion)
resumeToken:resumeToken];
}
diff --git a/Firestore/Source/Local/FSTReferenceSet.h b/Firestore/Source/Local/FSTReferenceSet.h
index 9d842cb..9a90a40 100644
--- a/Firestore/Source/Local/FSTReferenceSet.h
+++ b/Firestore/Source/Local/FSTReferenceSet.h
@@ -18,7 +18,8 @@
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Local/FSTGarbageCollector.h"
-#import "Firestore/Source/Model/FSTDocumentKeySet.h"
+
+#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
NS_ASSUME_NONNULL_BEGIN
@@ -47,13 +48,14 @@ NS_ASSUME_NONNULL_BEGIN
- (void)addReferenceToKey:(const firebase::firestore::model::DocumentKey &)key forID:(int)ID;
/** Add references to the given document keys for the given ID. */
-- (void)addReferencesToKeys:(FSTDocumentKeySet *)keys forID:(int)ID;
+- (void)addReferencesToKeys:(const firebase::firestore::model::DocumentKeySet &)keys forID:(int)ID;
/** Removes a reference to the given document key for the given ID. */
- (void)removeReferenceToKey:(const firebase::firestore::model::DocumentKey &)key forID:(int)ID;
/** Removes references to the given document keys for the given ID. */
-- (void)removeReferencesToKeys:(FSTDocumentKeySet *)keys forID:(int)ID;
+- (void)removeReferencesToKeys:(const firebase::firestore::model::DocumentKeySet &)keys
+ forID:(int)ID;
/** Clears all references with a given ID. Calls -removeReferenceToKey: for each key removed. */
- (void)removeReferencesForID:(int)ID;
@@ -62,7 +64,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)removeAllReferences;
/** Returns all of the document keys that have had references added for the given ID. */
-- (FSTDocumentKeySet *)referencedKeysForID:(int)ID;
+- (firebase::firestore::model::DocumentKeySet)referencedKeysForID:(int)ID;
@end
diff --git a/Firestore/Source/Local/FSTReferenceSet.mm b/Firestore/Source/Local/FSTReferenceSet.mm
index 14f5d47..6b34725 100644
--- a/Firestore/Source/Local/FSTReferenceSet.mm
+++ b/Firestore/Source/Local/FSTReferenceSet.mm
@@ -17,10 +17,12 @@
#import "Firestore/Source/Local/FSTReferenceSet.h"
#import "Firestore/Source/Local/FSTDocumentReference.h"
+#import "Firestore/third_party/Immutable/FSTImmutableSortedSet.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
@@ -68,20 +70,20 @@ NS_ASSUME_NONNULL_BEGIN
self.referencesByID = [self.referencesByID setByAddingObject:reference];
}
-- (void)addReferencesToKeys:(FSTDocumentKeySet *)keys forID:(int)ID {
- [keys enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
+- (void)addReferencesToKeys:(const DocumentKeySet &)keys forID:(int)ID {
+ for (const DocumentKey &key : keys) {
[self addReferenceToKey:key forID:ID];
- }];
+ }
}
- (void)removeReferenceToKey:(const DocumentKey &)key forID:(int)ID {
[self removeReference:[[FSTDocumentReference alloc] initWithKey:key ID:ID]];
}
-- (void)removeReferencesToKeys:(FSTDocumentKeySet *)keys forID:(int)ID {
- [keys enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
+- (void)removeReferencesToKeys:(const DocumentKeySet &)keys forID:(int)ID {
+ for (const DocumentKey &key : keys) {
[self removeReferenceToKey:key forID:ID];
- }];
+ }
}
- (void)removeReferencesForID:(int)ID {
@@ -109,17 +111,17 @@ NS_ASSUME_NONNULL_BEGIN
[self.garbageCollector addPotentialGarbageKey:reference.key];
}
-- (FSTDocumentKeySet *)referencedKeysForID:(int)ID {
+- (DocumentKeySet)referencedKeysForID:(int)ID {
FSTDocumentReference *start =
[[FSTDocumentReference alloc] initWithKey:DocumentKey::Empty() ID:ID];
FSTDocumentReference *end =
[[FSTDocumentReference alloc] initWithKey:DocumentKey::Empty() ID:(ID + 1)];
- __block FSTDocumentKeySet *keys = [FSTDocumentKeySet keySet];
+ __block DocumentKeySet keys;
[self.referencesByID enumerateObjectsFrom:start
to:end
usingBlock:^(FSTDocumentReference *reference, BOOL *stop) {
- keys = [keys setByAddingObject:reference.key];
+ keys = keys.insert(reference.key);
}];
return keys;
}
diff --git a/Firestore/Source/Model/FSTDocument.h b/Firestore/Source/Model/FSTDocument.h
index 47e4d28..0f8d4b3 100644
--- a/Firestore/Source/Model/FSTDocument.h
+++ b/Firestore/Source/Model/FSTDocument.h
@@ -18,10 +18,10 @@
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
@class FSTFieldValue;
@class FSTObjectValue;
-@class FSTSnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
@@ -32,14 +32,13 @@ NS_ASSUME_NONNULL_BEGIN
@interface FSTMaybeDocument : NSObject <NSCopying>
- (id)init __attribute__((unavailable("Abstract base class")));
- (const firebase::firestore::model::DocumentKey &)key;
-
-@property(nonatomic, readonly) FSTSnapshotVersion *version;
+- (const firebase::firestore::model::SnapshotVersion &)version;
@end
@interface FSTDocument : FSTMaybeDocument
+ (instancetype)documentWithData:(FSTObjectValue *)data
key:(firebase::firestore::model::DocumentKey)key
- version:(FSTSnapshotVersion *)version
+ version:(firebase::firestore::model::SnapshotVersion)version
hasLocalMutations:(BOOL)mutations;
- (nullable FSTFieldValue *)fieldForPath:(const firebase::firestore::model::FieldPath &)path;
@@ -51,7 +50,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface FSTDeletedDocument : FSTMaybeDocument
+ (instancetype)documentWithKey:(firebase::firestore::model::DocumentKey)key
- version:(FSTSnapshotVersion *)version;
+ version:(firebase::firestore::model::SnapshotVersion)version;
@end
/** An NSComparator suitable for comparing docs using only their keys. */
diff --git a/Firestore/Source/Model/FSTDocument.mm b/Firestore/Source/Model/FSTDocument.mm
index 9898c2a..8c4c801 100644
--- a/Firestore/Source/Model/FSTDocument.mm
+++ b/Firestore/Source/Model/FSTDocument.mm
@@ -18,37 +18,38 @@
#include <utility>
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Util/FSTAssert.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/util/hashing.h"
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
namespace util = firebase::firestore::util;
using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::FieldPath;
+using firebase::firestore::model::SnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
@interface FSTMaybeDocument ()
- (instancetype)initWithKey:(DocumentKey)key
- version:(FSTSnapshotVersion *)version NS_DESIGNATED_INITIALIZER;
+ version:(SnapshotVersion)version NS_DESIGNATED_INITIALIZER;
@end
@implementation FSTMaybeDocument {
DocumentKey _key;
+ SnapshotVersion _version;
}
-- (instancetype)initWithKey:(DocumentKey)key version:(FSTSnapshotVersion *)version {
- FSTAssert(!!version, @"Version must not be nil.");
+- (instancetype)initWithKey:(DocumentKey)key version:(SnapshotVersion)version {
self = [super init];
if (self) {
_key = std::move(key);
- _version = version;
+ _version = std::move(version);
}
return self;
}
@@ -62,25 +63,29 @@ NS_ASSUME_NONNULL_BEGIN
return _key;
}
+- (const SnapshotVersion &)version {
+ return _version;
+}
+
@end
@implementation FSTDocument
+ (instancetype)documentWithData:(FSTObjectValue *)data
key:(DocumentKey)key
- version:(FSTSnapshotVersion *)version
+ version:(SnapshotVersion)version
hasLocalMutations:(BOOL)mutations {
return [[FSTDocument alloc] initWithData:data
key:std::move(key)
- version:version
+ version:std::move(version)
hasLocalMutations:mutations];
}
- (instancetype)initWithData:(FSTObjectValue *)data
key:(DocumentKey)key
- version:(FSTSnapshotVersion *)version
+ version:(SnapshotVersion)version
hasLocalMutations:(BOOL)mutations {
- self = [super initWithKey:std::move(key) version:version];
+ self = [super initWithKey:std::move(key) version:std::move(version)];
if (self) {
_data = data;
_localMutations = mutations;
@@ -97,21 +102,22 @@ NS_ASSUME_NONNULL_BEGIN
}
FSTDocument *otherDoc = other;
- return [self.key isEqual:otherDoc.key] && [self.version isEqual:otherDoc.version] &&
+ return [self.key isEqual:otherDoc.key] && self.version == otherDoc.version &&
[self.data isEqual:otherDoc.data] && self.hasLocalMutations == otherDoc.hasLocalMutations;
}
- (NSUInteger)hash {
NSUInteger result = [self.key hash];
- result = result * 31 + [self.version hash];
+ result = result * 31 + self.version.Hash();
result = result * 31 + [self.data hash];
result = result * 31 + (self.hasLocalMutations ? 1 : 0);
return result;
}
- (NSString *)description {
- return [NSString stringWithFormat:@"<FSTDocument: key:%s version:%@ localMutations:%@ data:%@>",
- self.key.ToString().c_str(), self.version,
+ return [NSString stringWithFormat:@"<FSTDocument: key:%s version:%s localMutations:%@ data:%@>",
+ self.key.ToString().c_str(),
+ self.version.timestamp().ToString().c_str(),
self.localMutations ? @"YES" : @"NO", self.data];
}
@@ -123,8 +129,8 @@ NS_ASSUME_NONNULL_BEGIN
@implementation FSTDeletedDocument
-+ (instancetype)documentWithKey:(DocumentKey)key version:(FSTSnapshotVersion *)version {
- return [[FSTDeletedDocument alloc] initWithKey:std::move(key) version:version];
++ (instancetype)documentWithKey:(DocumentKey)key version:(SnapshotVersion)version {
+ return [[FSTDeletedDocument alloc] initWithKey:std::move(key) version:std::move(version)];
}
- (BOOL)isEqual:(id)other {
@@ -136,12 +142,12 @@ NS_ASSUME_NONNULL_BEGIN
}
FSTDocument *otherDoc = other;
- return [self.key isEqual:otherDoc.key] && [self.version isEqual:otherDoc.version];
+ return [self.key isEqual:otherDoc.key] && self.version == otherDoc.version;
}
- (NSUInteger)hash {
NSUInteger result = [self.key hash];
- result = result * 31 + [self.version hash];
+ result = result * 31 + self.version.Hash();
return result;
}
diff --git a/Firestore/Source/Model/FSTDocumentKey.mm b/Firestore/Source/Model/FSTDocumentKey.mm
index 679d7a6..d29df86 100644
--- a/Firestore/Source/Model/FSTDocumentKey.mm
+++ b/Firestore/Source/Model/FSTDocumentKey.mm
@@ -23,6 +23,7 @@
#import "Firestore/Source/Util/FSTAssert.h"
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/hashing.h"
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
namespace util = firebase::firestore::util;
@@ -72,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (NSUInteger)hash {
- return _path.Hash();
+ return util::Hash(_path);
}
- (NSString *)description {
diff --git a/Firestore/Source/Model/FSTDocumentKeySet.mm b/Firestore/Source/Model/FSTDocumentKeySet.mm
deleted file mode 100644
index f07b785..0000000
--- a/Firestore/Source/Model/FSTDocumentKeySet.mm
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright 2017 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#import "Firestore/Source/Model/FSTDocumentKeySet.h"
-
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@implementation FSTImmutableSortedSet (FSTDocumentKey)
-
-+ (instancetype)keySet {
- return [FSTDocumentKeySet setWithComparator:FSTDocumentKeyComparator];
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Model/FSTDocumentVersionDictionary.h b/Firestore/Source/Model/FSTDocumentVersionDictionary.h
deleted file mode 100644
index 674614e..0000000
--- a/Firestore/Source/Model/FSTDocumentVersionDictionary.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright 2017 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#import <Foundation/Foundation.h>
-
-#import "Firestore/third_party/Immutable/FSTImmutableSortedDictionary.h"
-
-@class FSTDocumentKey;
-@class FSTSnapshotVersion;
-
-NS_ASSUME_NONNULL_BEGIN
-
-/** A map of key to version number. */
-typedef FSTImmutableSortedDictionary<FSTDocumentKey *, FSTSnapshotVersion *>
- FSTDocumentVersionDictionary;
-
-/**
- * Extension to FSTImmutableSortedDictionary that allows natural construction of
- * FSTDocumentVersionDictionary.
- */
-@interface FSTImmutableSortedDictionary (FSTDocumentVersionDictionary)
-
-+ (instancetype)documentVersionDictionary;
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Model/FSTDocumentVersionDictionary.mm b/Firestore/Source/Model/FSTDocumentVersionDictionary.mm
deleted file mode 100644
index 870e082..0000000
--- a/Firestore/Source/Model/FSTDocumentVersionDictionary.mm
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2017 Google
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#import "Firestore/Source/Model/FSTDocumentVersionDictionary.h"
-
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#import "Firestore/Source/Model/FSTDocumentKey.h"
-
-NS_ASSUME_NONNULL_BEGIN
-
-@implementation FSTImmutableSortedDictionary (FSTDocumentVersionDictionary)
-
-+ (instancetype)documentVersionDictionary {
- static FSTDocumentVersionDictionary *singleton;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- singleton = [FSTDocumentVersionDictionary dictionaryWithComparator:FSTDocumentKeyComparator];
- });
- return singleton;
-}
-
-@end
-
-NS_ASSUME_NONNULL_END
diff --git a/Firestore/Source/Model/FSTMutation.h b/Firestore/Source/Model/FSTMutation.h
index 7261f30..0acec15 100644
--- a/Firestore/Source/Model/FSTMutation.h
+++ b/Firestore/Source/Model/FSTMutation.h
@@ -24,13 +24,15 @@
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
#include "Firestore/core/src/firebase/firestore/model/field_transform.h"
#include "Firestore/core/src/firebase/firestore/model/precondition.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
+#include "absl/types/optional.h"
+
@class FSTDocument;
@class FSTFieldValue;
@class FSTMaybeDocument;
@class FSTObjectValue;
-@class FSTSnapshotVersion;
@class FIRTimestamp;
NS_ASSUME_NONNULL_BEGIN
@@ -40,12 +42,12 @@ NS_ASSUME_NONNULL_BEGIN
@interface FSTMutationResult : NSObject
- (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithVersion:(FSTSnapshotVersion *_Nullable)version
+- (instancetype)initWithVersion:(absl::optional<firebase::firestore::model::SnapshotVersion>)version
transformResults:(NSArray<FSTFieldValue *> *_Nullable)transformResults
NS_DESIGNATED_INITIALIZER;
/** The version at which the mutation was committed or null for a delete. */
-@property(nonatomic, strong, readonly, nullable) FSTSnapshotVersion *version;
+- (const absl::optional<firebase::firestore::model::SnapshotVersion> &)version;
/**
* The resulting fields returned from the backend after a FSTTransformMutation has been committed.
diff --git a/Firestore/Source/Model/FSTMutation.mm b/Firestore/Source/Model/FSTMutation.mm
index 3432a7c..82a535e 100644
--- a/Firestore/Source/Model/FSTMutation.mm
+++ b/Firestore/Source/Model/FSTMutation.mm
@@ -23,7 +23,6 @@
#import "FIRTimestamp.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Util/FSTAssert.h"
@@ -36,6 +35,8 @@
#include "Firestore/core/src/firebase/firestore/model/precondition.h"
#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
+#include "absl/types/optional.h"
+
using firebase::firestore::model::ArrayTransform;
using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::FieldMask;
@@ -43,23 +44,30 @@ using firebase::firestore::model::FieldPath;
using firebase::firestore::model::FieldTransform;
using firebase::firestore::model::Precondition;
using firebase::firestore::model::ServerTimestampTransform;
+using firebase::firestore::model::SnapshotVersion;
using firebase::firestore::model::TransformOperation;
NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTMutationResult
-@implementation FSTMutationResult
+@implementation FSTMutationResult {
+ absl::optional<SnapshotVersion> _version;
+}
-- (instancetype)initWithVersion:(nullable FSTSnapshotVersion *)version
+- (instancetype)initWithVersion:(absl::optional<SnapshotVersion>)version
transformResults:(nullable NSArray<FSTFieldValue *> *)transformResults {
if (self = [super init]) {
- _version = version;
+ _version = std::move(version);
_transformResults = transformResults;
}
return self;
}
+- (const absl::optional<SnapshotVersion> &)version {
+ return _version;
+}
+
@end
#pragma mark - FSTMutation
@@ -157,7 +165,7 @@ NS_ASSUME_NONNULL_BEGIN
// If the document didn't exist before, create it.
return [FSTDocument documentWithData:self.value
key:self.key
- version:[FSTSnapshotVersion noVersion]
+ version:SnapshotVersion::None()
hasLocalMutations:hasLocalMutations];
}
@@ -239,10 +247,10 @@ NS_ASSUME_NONNULL_BEGIN
if (!maybeDoc || [maybeDoc isMemberOfClass:[FSTDeletedDocument class]]) {
// Precondition applied, so create the document if necessary
const DocumentKey &key = maybeDoc ? maybeDoc.key : self.key;
- FSTSnapshotVersion *version = maybeDoc ? maybeDoc.version : [FSTSnapshotVersion noVersion];
+ SnapshotVersion version = maybeDoc ? maybeDoc.version : SnapshotVersion::None();
maybeDoc = [FSTDocument documentWithData:[FSTObjectValue objectValue]
key:key
- version:version
+ version:std::move(version)
hasLocalMutations:hasLocalMutations];
}
@@ -384,30 +392,15 @@ serverTransformResultsWithBaseDocument:(nullable FSTMaybeDocument *)baseDocument
for (NSUInteger i = 0; i < serverTransformResults.count; i++) {
const FieldTransform &fieldTransform = self.fieldTransforms[i];
+ const TransformOperation &transform = fieldTransform.transformation();
+
FSTFieldValue *previousValue = nil;
if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()];
}
- FSTFieldValue *transformResult;
- // The server just sends null as the transform result for array union / remove operations, so
- // we have to calculate a result the same as we do for local applications.
- if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayUnion) {
- transformResult = [self
- arrayUnionResultWithElements:ArrayTransform::Elements(fieldTransform.transformation())
- previousValue:previousValue];
-
- } else if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayRemove) {
- transformResult = [self
- arrayRemoveResultWithElements:ArrayTransform::Elements(fieldTransform.transformation())
- previousValue:previousValue];
-
- } else {
- // Just use the server-supplied result.
- transformResult = serverTransformResults[i];
- }
-
- [transformResults addObject:transformResult];
+ [transformResults
+ addObject:transform.ApplyToRemoteDocument(previousValue, serverTransformResults[i])];
}
return transformResults;
}
@@ -426,77 +419,18 @@ serverTransformResultsWithBaseDocument:(nullable FSTMaybeDocument *)baseDocument
writeTime:(FIRTimestamp *)localWriteTime {
NSMutableArray<FSTFieldValue *> *transformResults = [NSMutableArray array];
for (const FieldTransform &fieldTransform : self.fieldTransforms) {
+ const TransformOperation &transform = fieldTransform.transformation();
+
FSTFieldValue *previousValue = nil;
if ([baseDocument isMemberOfClass:[FSTDocument class]]) {
previousValue = [((FSTDocument *)baseDocument) fieldForPath:fieldTransform.path()];
}
- FSTFieldValue *transformResult;
- if (fieldTransform.transformation().type() == TransformOperation::Type::ServerTimestamp) {
- transformResult =
- [FSTServerTimestampValue serverTimestampValueWithLocalWriteTime:localWriteTime
- previousValue:previousValue];
-
- } else if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayUnion) {
- transformResult = [self
- arrayUnionResultWithElements:ArrayTransform::Elements(fieldTransform.transformation())
- previousValue:previousValue];
-
- } else if (fieldTransform.transformation().type() == TransformOperation::Type::ArrayRemove) {
- transformResult = [self
- arrayRemoveResultWithElements:ArrayTransform::Elements(fieldTransform.transformation())
- previousValue:previousValue];
-
- } else {
- FSTFail(@"Encountered unknown transform: %d type", fieldTransform.transformation().type());
- }
-
- [transformResults addObject:transformResult];
+ [transformResults addObject:transform.ApplyToLocalView(previousValue, localWriteTime)];
}
return transformResults;
}
-/**
- * Transforms the provided `previousValue` via the provided `elements`. Used both for local
- * application and after server acknowledgement.
- */
-- (FSTFieldValue *)arrayUnionResultWithElements:(const std::vector<FSTFieldValue *> &)elements
- previousValue:(FSTFieldValue *)previousValue {
- NSMutableArray<FSTFieldValue *> *result = [self coercedFieldValuesArray:previousValue];
- for (FSTFieldValue *element : elements) {
- if (![result containsObject:element]) {
- [result addObject:element];
- }
- }
- return [[FSTArrayValue alloc] initWithValueNoCopy:result];
-}
-
-/**
- * Transforms the provided `previousValue` via the provided `elements`. Used both for local
- * application and after server acknowledgement.
- */
-- (FSTFieldValue *)arrayRemoveResultWithElements:(const std::vector<FSTFieldValue *> &)elements
- previousValue:(FSTFieldValue *)previousValue {
- NSMutableArray<FSTFieldValue *> *result = [self coercedFieldValuesArray:previousValue];
- for (FSTFieldValue *element : elements) {
- [result removeObject:element];
- }
- return [[FSTArrayValue alloc] initWithValueNoCopy:result];
-}
-
-/**
- * Inspects the provided value, returning a mutable copy of the internal array if it's an
- * FSTArrayValue and an empty mutable array if it's nil or any other type of FSTFieldValue.
- */
-- (NSMutableArray<FSTFieldValue *> *)coercedFieldValuesArray:(nullable FSTFieldValue *)value {
- if ([value isMemberOfClass:[FSTArrayValue class]]) {
- return [NSMutableArray arrayWithArray:((FSTArrayValue *)value).internalValue];
- } else {
- // coerce to empty array.
- return [NSMutableArray array];
- }
-}
-
- (FSTObjectValue *)transformObject:(FSTObjectValue *)objectValue
transformResults:(NSArray<FSTFieldValue *> *)transformResults {
FSTAssert(transformResults.count == self.fieldTransforms.size(),
@@ -556,7 +490,7 @@ serverTransformResultsWithBaseDocument:(nullable FSTMaybeDocument *)baseDocument
FSTAssert([maybeDoc.key isEqual:self.key], @"Can only delete a document with the same key");
}
- return [FSTDeletedDocument documentWithKey:self.key version:[FSTSnapshotVersion noVersion]];
+ return [FSTDeletedDocument documentWithKey:self.key version:SnapshotVersion::None()];
}
@end
diff --git a/Firestore/Source/Model/FSTMutationBatch.h b/Firestore/Source/Model/FSTMutationBatch.h
index 3c82338..761a885 100644
--- a/Firestore/Source/Model/FSTMutationBatch.h
+++ b/Firestore/Source/Model/FSTMutationBatch.h
@@ -16,17 +16,29 @@
#import <Foundation/Foundation.h>
+#include <unordered_map>
+
#import "Firestore/Source/Core/FSTTypes.h"
-#import "Firestore/Source/Model/FSTDocumentKeySet.h"
-#import "Firestore/Source/Model/FSTDocumentVersionDictionary.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
@class FSTMutation;
@class FIRTimestamp;
@class FSTMutationResult;
@class FSTMutationBatchResult;
-@class FSTSnapshotVersion;
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+// TODO(wilhuff): make this type a member of MutationBatchResult once that's a C++ class.
+using DocumentVersionMap = std::unordered_map<DocumentKey, SnapshotVersion, DocumentKeyHash>;
+
+} // namespace model
+} // namespace firestore
+} // namespace firebase
NS_ASSUME_NONNULL_BEGIN
@@ -85,7 +97,7 @@ extern const FSTBatchID kFSTBatchIDUnknown;
- (FSTMutationBatch *)toTombstone;
/** Returns the set of unique keys referenced by all mutations in the batch. */
-- (FSTDocumentKeySet *)keys;
+- (firebase::firestore::model::DocumentKeySet)keys;
@property(nonatomic, assign, readonly) FSTBatchID batchID;
@property(nonatomic, strong, readonly) FIRTimestamp *localWriteTime;
@@ -106,15 +118,17 @@ extern const FSTBatchID kFSTBatchIDUnknown;
* (as docVersions).
*/
+ (instancetype)resultWithBatch:(FSTMutationBatch *)batch
- commitVersion:(FSTSnapshotVersion *)commitVersion
+ commitVersion:(firebase::firestore::model::SnapshotVersion)commitVersion
mutationResults:(NSArray<FSTMutationResult *> *)mutationResults
streamToken:(nullable NSData *)streamToken;
+- (const firebase::firestore::model::SnapshotVersion &)commitVersion;
+
@property(nonatomic, strong, readonly) FSTMutationBatch *batch;
-@property(nonatomic, strong, readonly) FSTSnapshotVersion *commitVersion;
@property(nonatomic, strong, readonly) NSArray<FSTMutationResult *> *mutationResults;
@property(nonatomic, strong, readonly, nullable) NSData *streamToken;
-@property(nonatomic, strong, readonly) FSTDocumentVersionDictionary *docVersions;
+
+- (const firebase::firestore::model::DocumentVersionMap &)docVersions;
@end
diff --git a/Firestore/Source/Model/FSTMutationBatch.mm b/Firestore/Source/Model/FSTMutationBatch.mm
index e62a72c..1e9189c 100644
--- a/Firestore/Source/Model/FSTMutationBatch.mm
+++ b/Firestore/Source/Model/FSTMutationBatch.mm
@@ -16,16 +16,19 @@
#import "Firestore/Source/Model/FSTMutationBatch.h"
+#include <utility>
+
#import "FIRTimestamp.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTMutation.h"
#import "Firestore/Source/Util/FSTAssert.h"
-#include "Firestore/core/src/firebase/firestore/model/document_key.h"
-
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::DocumentKeyHash;
+using firebase::firestore::model::SnapshotVersion;
+using firebase::firestore::model::DocumentKeySet;
+using firebase::firestore::model::DocumentVersionMap;
NS_ASSUME_NONNULL_BEGIN
@@ -113,10 +116,10 @@ const FSTBatchID kFSTBatchIDUnknown = -1;
}
// TODO(klimt): This could use NSMutableDictionary instead.
-- (FSTDocumentKeySet *)keys {
- FSTDocumentKeySet *set = [FSTDocumentKeySet keySet];
+- (DocumentKeySet)keys {
+ DocumentKeySet set;
for (FSTMutation *mutation in self.mutations) {
- set = [set setByAddingObject:mutation.key];
+ set = set.insert(mutation.key);
}
return set;
}
@@ -127,56 +130,66 @@ const FSTBatchID kFSTBatchIDUnknown = -1;
@interface FSTMutationBatchResult ()
- (instancetype)initWithBatch:(FSTMutationBatch *)batch
- commitVersion:(FSTSnapshotVersion *)commitVersion
+ commitVersion:(SnapshotVersion)commitVersion
mutationResults:(NSArray<FSTMutationResult *> *)mutationResults
streamToken:(nullable NSData *)streamToken
- docVersions:(FSTDocumentVersionDictionary *)docVersions NS_DESIGNATED_INITIALIZER;
+ docVersions:(DocumentVersionMap)docVersions NS_DESIGNATED_INITIALIZER;
@end
-@implementation FSTMutationBatchResult
+@implementation FSTMutationBatchResult {
+ SnapshotVersion _commitVersion;
+ DocumentVersionMap _docVersions;
+}
- (instancetype)initWithBatch:(FSTMutationBatch *)batch
- commitVersion:(FSTSnapshotVersion *)commitVersion
+ commitVersion:(SnapshotVersion)commitVersion
mutationResults:(NSArray<FSTMutationResult *> *)mutationResults
streamToken:(nullable NSData *)streamToken
- docVersions:(FSTDocumentVersionDictionary *)docVersions {
+ docVersions:(DocumentVersionMap)docVersions {
if (self = [super init]) {
_batch = batch;
- _commitVersion = commitVersion;
+ _commitVersion = std::move(commitVersion);
_mutationResults = mutationResults;
_streamToken = streamToken;
- _docVersions = docVersions;
+ _docVersions = std::move(docVersions);
}
return self;
}
+- (const SnapshotVersion &)commitVersion {
+ return _commitVersion;
+}
+
+- (const DocumentVersionMap &)docVersions {
+ return _docVersions;
+}
+
+ (instancetype)resultWithBatch:(FSTMutationBatch *)batch
- commitVersion:(FSTSnapshotVersion *)commitVersion
+ commitVersion:(SnapshotVersion)commitVersion
mutationResults:(NSArray<FSTMutationResult *> *)mutationResults
streamToken:(nullable NSData *)streamToken {
FSTAssert(batch.mutations.count == mutationResults.count,
@"Mutations sent %lu must equal results received %lu",
(unsigned long)batch.mutations.count, (unsigned long)mutationResults.count);
- FSTDocumentVersionDictionary *docVersions =
- [FSTDocumentVersionDictionary documentVersionDictionary];
+ DocumentVersionMap docVersions;
NSArray<FSTMutation *> *mutations = batch.mutations;
for (NSUInteger i = 0; i < mutations.count; i++) {
- FSTSnapshotVersion *_Nullable version = mutationResults[i].version;
+ absl::optional<SnapshotVersion> version = mutationResults[i].version;
if (!version) {
// deletes don't have a version, so we substitute the commitVersion
// of the entire batch.
version = commitVersion;
}
- docVersions = [docVersions dictionaryBySettingObject:version forKey:mutations[i].key];
+ docVersions[mutations[i].key] = version.value();
}
return [[FSTMutationBatchResult alloc] initWithBatch:batch
- commitVersion:commitVersion
+ commitVersion:std::move(commitVersion)
mutationResults:mutationResults
streamToken:streamToken
- docVersions:docVersions];
+ docVersions:std::move(docVersions)];
}
@end
diff --git a/Firestore/Source/Public/FIRDocumentReference.h b/Firestore/Source/Public/FIRDocumentReference.h
index 4aa8c45..7baa30a 100644
--- a/Firestore/Source/Public/FIRDocumentReference.h
+++ b/Firestore/Source/Public/FIRDocumentReference.h
@@ -92,6 +92,23 @@ NS_SWIFT_NAME(DocumentReference)
- (void)setData:(NSDictionary<NSString *, id> *)documentData merge:(BOOL)merge;
/**
+ * Writes to the document referred to by `document` and only replace the fields
+ * specified under `mergeFields`. Any field that is not specified in `mergeFields`
+ * is ignored and remains untouched. If the document doesn't yet exist,
+ * this method creates it and then sets the data.
+ *
+ * It is an error to include a field in `mergeFields` that does not have a corresponding
+ * value in the `data` dictionary.
+ *
+ * @param documentData An `NSDictionary` containing the fields that make up the document
+ * to be written.
+ * @param mergeFields An `NSArray` that contains a list of `NSString` or `FIRFieldPath` elements
+ * specifying which fields to merge. Fields can contain dots to reference nested fields within
+ * the document.
+ */
+- (void)setData:(NSDictionary<NSString *, id> *)documentData mergeFields:(NSArray<id> *)mergeFields;
+
+/**
* Overwrites the document referred to by this `FIRDocumentReference`. If no document exists, it
* is created. If a document already exists, it is overwritten.
*
@@ -121,6 +138,28 @@ NS_SWIFT_NAME(DocumentReference)
completion:(nullable void (^)(NSError *_Nullable error))completion;
/**
+ * Writes to the document referred to by `document` and only replace the fields
+ * specified under `mergeFields`. Any field that is not specified in `mergeFields`
+ * is ignored and remains untouched. If the document doesn't yet exist,
+ * this method creates it and then sets the data.
+ *
+ * It is an error to include a field in `mergeFields` that does not have a corresponding
+ * value in the `data` dictionary.
+ *
+ * @param documentData An `NSDictionary` containing the fields that make up the document
+ * to be written.
+ * @param mergeFields An `NSArray` that contains a list of `NSString` or `FIRFieldPath` elements
+ * specifying which fields to merge. Fields can contain dots to reference nested fields within
+ * the document.
+ * @param completion A block to execute once the document has been successfully written to the
+ * server. This block will not be called while the client is offline, though local
+ * changes will be visible immediately.
+ */
+- (void)setData:(NSDictionary<NSString *, id> *)documentData
+ mergeFields:(NSArray<id> *)mergeFields
+ completion:(nullable void (^)(NSError *_Nullable error))completion;
+
+/**
* Updates fields in the document referred to by this `FIRDocumentReference`.
* If the document does not exist, the update fails (specify a completion block to be notified).
*
diff --git a/Firestore/Source/Public/FIRTransaction.h b/Firestore/Source/Public/FIRTransaction.h
index 2fa4430..e53414d 100644
--- a/Firestore/Source/Public/FIRTransaction.h
+++ b/Firestore/Source/Public/FIRTransaction.h
@@ -65,6 +65,30 @@ NS_SWIFT_NAME(Transaction)
// clang-format on
/**
+ * Writes to the document referred to by `document` and only replace the fields
+ * specified under `mergeFields`. Any field that is not specified in `mergeFields`
+ * is ignored and remains untouched. If the document doesn't yet exist,
+ * this method creates it and then sets the data.
+ *
+ * It is an error to include a field in `mergeFields` that does not have a corresponding
+ * value in the `data` dictionary.
+ *
+ * @param data An `NSDictionary` containing the fields that make up the document
+ * to be written.
+ * @param document A reference to the document whose data should be overwritten.
+ * @param mergeFields An `NSArray` that contains a list of `NSString` or `FIRFieldPath` elements
+ * specifying which fields to merge. Fields can contain dots to reference nested fields within
+ * the document.
+ * @return This `FIRTransaction` instance. Used for chaining method calls.
+ */
+// clang-format off
+- (FIRTransaction *)setData:(NSDictionary<NSString *, id> *)data
+ forDocument:(FIRDocumentReference *)document
+ mergeFields:(NSArray<id> *)mergeFields
+ NS_SWIFT_NAME(setData(_:forDocument:mergeFields:));
+// clang-format on
+
+/**
* Updates fields in the document referred to by `document`.
* If the document does not exist, the transaction will fail.
*
diff --git a/Firestore/Source/Public/FIRWriteBatch.h b/Firestore/Source/Public/FIRWriteBatch.h
index 1568723..22d1b16 100644
--- a/Firestore/Source/Public/FIRWriteBatch.h
+++ b/Firestore/Source/Public/FIRWriteBatch.h
@@ -68,6 +68,29 @@ NS_SWIFT_NAME(WriteBatch)
// clang-format on
/**
+ * Writes to the document referred to by `document` and only replace the fields
+ * specified under `mergeFields`. Any field that is not specified in `mergeFields`
+ * is ignored and remains untouched. If the document doesn't yet exist,
+ * this method creates it and then sets the data.
+ *
+ * It is an error to include a field in `mergeFields` that does not have a corresponding
+ * value in the `data` dictionary.
+ *
+ * @param data An `NSDictionary` that contains the fields and data to write to the document.
+ * @param document A reference to the document whose data should be overwritten.
+ * @param mergeFields An `NSArray` that contains a list of `NSString` or `FIRFieldPath` elements
+ * specifying which fields to merge. Fields can contain dots to reference nested fields within
+ * the document.
+ * @return This `FIRWriteBatch` instance. Used for chaining method calls.
+ */
+// clang-format off
+- (FIRWriteBatch *)setData:(NSDictionary<NSString *, id> *)data
+ forDocument:(FIRDocumentReference *)document
+ mergeFields:(NSArray<id> *)mergeFields
+ NS_SWIFT_NAME(setData(_:forDocument:mergeFields:));
+// clang-format on
+
+/**
* Updates fields in the document referred to by `document`.
* If document does not exist, the write batch will fail.
*
diff --git a/Firestore/Source/Remote/FSTDatastore.h b/Firestore/Source/Remote/FSTDatastore.h
index b3ba46c..da14b6e 100644
--- a/Firestore/Source/Remote/FSTDatastore.h
+++ b/Firestore/Source/Remote/FSTDatastore.h
@@ -31,7 +31,6 @@
@class FSTMutationResult;
@class FSTQueryData;
@class FSTSerializerBeta;
-@class FSTSnapshotVersion;
@class FSTWatchChange;
@class FSTWatchStream;
@class FSTWriteStream;
diff --git a/Firestore/Source/Remote/FSTRemoteEvent.h b/Firestore/Source/Remote/FSTRemoteEvent.h
index 0f6b6c7..c84e34d 100644
--- a/Firestore/Source/Remote/FSTRemoteEvent.h
+++ b/Firestore/Source/Remote/FSTRemoteEvent.h
@@ -20,14 +20,14 @@
#import "Firestore/Source/Core/FSTTypes.h"
#import "Firestore/Source/Model/FSTDocumentDictionary.h"
-#import "Firestore/Source/Model/FSTDocumentKeySet.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key_set.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
@class FSTDocument;
@class FSTExistenceFilter;
@class FSTMaybeDocument;
-@class FSTSnapshotVersion;
@class FSTWatchChange;
@class FSTQueryData;
@@ -43,6 +43,15 @@ NS_ASSUME_NONNULL_BEGIN
* base class.
*/
@interface FSTTargetMapping : NSObject
+
+/**
+ * Strips out mapping changes that aren't actually changes. That is, if the document already
+ * existed in the target, and is being added in the target, and this is not a reset, we can
+ * skip doing the work to associate the document with the target because it has already been done.
+ */
+- (void)filterUpdatesUsingExistingKeys:
+ (const firebase::firestore::model::DocumentKeySet &)existingKeys;
+
@end
#pragma mark - FSTResetMapping
@@ -57,7 +66,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (FSTResetMapping *)mappingWithDocuments:(NSArray<FSTDocument *> *)documents;
/** The new set of documents for the target. */
-@property(nonatomic, strong, readonly) FSTDocumentKeySet *documents;
+- (const firebase::firestore::model::DocumentKeySet &)documents;
@end
#pragma mark - FSTUpdateMapping
@@ -74,12 +83,13 @@ NS_ASSUME_NONNULL_BEGIN
+ (FSTUpdateMapping *)mappingWithAddedDocuments:(NSArray<FSTDocument *> *)added
removedDocuments:(NSArray<FSTDocument *> *)removed;
-- (FSTDocumentKeySet *)applyTo:(FSTDocumentKeySet *)keys;
+- (firebase::firestore::model::DocumentKeySet)applyTo:
+ (const firebase::firestore::model::DocumentKeySet &)keys;
/** The documents added to the target. */
-@property(nonatomic, strong, readonly) FSTDocumentKeySet *addedDocuments;
+- (const firebase::firestore::model::DocumentKeySet &)addedDocuments;
/** The documents removed from the target. */
-@property(nonatomic, strong, readonly) FSTDocumentKeySet *removedDocuments;
+- (const firebase::firestore::model::DocumentKeySet &)removedDocuments;
@end
#pragma mark - FSTTargetChange
@@ -107,6 +117,12 @@ typedef NS_ENUM(NSUInteger, FSTCurrentStatusUpdate) {
@interface FSTTargetChange : NSObject
/**
+ * Creates a new target change with the given SnapshotVersion.
+ */
+- (instancetype)initWithSnapshotVersion:
+ (firebase::firestore::model::SnapshotVersion)snapshotVersion;
+
+/**
* Creates a new target change with the given documents. Instances of FSTDocument are considered
* added. Instance of FSTDeletedDocument are considered removed. This is intended primarily for
* testing.
@@ -115,6 +131,12 @@ typedef NS_ENUM(NSUInteger, FSTCurrentStatusUpdate) {
currentStatusUpdate:(FSTCurrentStatusUpdate)currentStatusUpdate;
/**
+ * The snapshot version representing the last state at which this target received a consistent
+ * snapshot from the backend.
+ */
+- (const firebase::firestore::model::SnapshotVersion &)snapshotVersion;
+
+/**
* The new "current" (synced) status of this target. Set to CurrentStatusUpdateNone if the status
* should not be updated. Note "current" has special meaning for in the RPC protocol that implies
* that a target is both up-to-date and consistent with the rest of the watch stream.
@@ -125,12 +147,6 @@ typedef NS_ENUM(NSUInteger, FSTCurrentStatusUpdate) {
@property(nonatomic, strong, readonly) FSTTargetMapping *mapping;
/**
- * The snapshot version representing the last state at which this target received a consistent
- * snapshot from the backend.
- */
-@property(nonatomic, strong, readonly) FSTSnapshotVersion *snapshotVersion;
-
-/**
* An opaque, server-assigned token that allows watching a query to be resumed after disconnecting
* without retransmitting all the data that matches the query. The resume token essentially
* identifies a point in time from which the server should resume sending results.
@@ -147,14 +163,15 @@ typedef NS_ENUM(NSUInteger, FSTCurrentStatusUpdate) {
*/
@interface FSTRemoteEvent : NSObject
-+ (instancetype)
-eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
- targetChanges:(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
- documentUpdates:
- (std::map<firebase::firestore::model::DocumentKey, FSTMaybeDocument *>)documentUpdates;
+- (instancetype)
+initWithSnapshotVersion:(firebase::firestore::model::SnapshotVersion)snapshotVersion
+ targetChanges:(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
+ documentUpdates:
+ (std::map<firebase::firestore::model::DocumentKey, FSTMaybeDocument *>)documentUpdates
+ limboDocuments:(firebase::firestore::model::DocumentKeySet)limboDocuments;
/** The snapshot version this event brings us up to. */
-@property(nonatomic, strong, readonly) FSTSnapshotVersion *snapshotVersion;
+- (const firebase::firestore::model::SnapshotVersion &)snapshotVersion;
/** A map from target to changes to the target. See TargetChange. */
@property(nonatomic, strong, readonly)
@@ -166,6 +183,8 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
*/
- (const std::map<firebase::firestore::model::DocumentKey, FSTMaybeDocument *> &)documentUpdates;
+- (const firebase::firestore::model::DocumentKeySet &)limboDocumentChanges;
+
/** Adds a document update to this remote event */
- (void)addDocumentUpdate:(FSTMaybeDocument *)document;
@@ -175,14 +194,6 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
- (void)synthesizeDeleteForLimboTargetChange:(FSTTargetChange *)targetChange
key:(const firebase::firestore::model::DocumentKey &)key;
-/**
- * Strips out mapping changes that aren't actually changes. That is, if the document already
- * existed in the target, and is being added in the target, and this is not a reset, we can
- * skip doing the work to associate the document with the target because it has already been done.
- */
-- (void)filterUpdatesFromTargetChange:(FSTTargetChange *)targetChange
- existingDocuments:(FSTDocumentKeySet *)existingDocuments;
-
@end
#pragma mark - FSTWatchChangeAggregator
@@ -194,7 +205,7 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
@interface FSTWatchChangeAggregator : NSObject
- (instancetype)
-initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
+initWithSnapshotVersion:(firebase::firestore::model::SnapshotVersion)snapshotVersion
listenTargets:(NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)listenTargets
pendingTargetResponses:(NSDictionary<FSTBoxedTargetID *, NSNumber *> *)pendingTargetResponses
NS_DESIGNATED_INITIALIZER;
diff --git a/Firestore/Source/Remote/FSTRemoteEvent.mm b/Firestore/Source/Remote/FSTRemoteEvent.mm
index 30aa0d3..438072e 100644
--- a/Firestore/Source/Remote/FSTRemoteEvent.mm
+++ b/Firestore/Source/Remote/FSTRemoteEvent.mm
@@ -19,16 +19,19 @@
#include <map>
#include <utility>
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
+#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Remote/FSTWatchChange.h"
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTClasses.h"
#import "Firestore/Source/Util/FSTLogger.h"
-
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/util/hashing.h"
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::SnapshotVersion;
+using firebase::firestore::util::Hash;
+using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
@@ -54,32 +57,38 @@ NS_ASSUME_NONNULL_BEGIN
@throw FSTAbstractMethodException(); // NOLINT
}
+- (void)filterUpdatesUsingExistingKeys:(const DocumentKeySet &)existingKeys {
+ @throw FSTAbstractMethodException(); // NOLINT
+}
+
@end
#pragma mark - FSTResetMapping
-@interface FSTResetMapping ()
-@property(nonatomic, strong) FSTDocumentKeySet *documents;
-@end
-
-@implementation FSTResetMapping
+@implementation FSTResetMapping {
+ DocumentKeySet _documents;
+}
+ (instancetype)mappingWithDocuments:(NSArray<FSTDocument *> *)documents {
- FSTResetMapping *mapping = [[FSTResetMapping alloc] init];
+ DocumentKeySet keys;
for (FSTDocument *doc in documents) {
- mapping.documents = [mapping.documents setByAddingObject:doc.key];
+ keys = keys.insert(doc.key);
}
- return mapping;
+ return [[FSTResetMapping alloc] initWithDocuments:std::move(keys)];
}
-- (instancetype)init {
+- (instancetype)initWithDocuments:(DocumentKeySet)documents {
self = [super init];
if (self) {
- _documents = [FSTDocumentKeySet keySet];
+ _documents = std::move(documents);
}
return self;
}
+- (const DocumentKeySet &)documents {
+ return _documents;
+}
+
- (BOOL)isEqual:(id)other {
if (other == self) {
return YES;
@@ -89,53 +98,66 @@ NS_ASSUME_NONNULL_BEGIN
}
FSTResetMapping *otherMapping = (FSTResetMapping *)other;
- return [self.documents isEqual:otherMapping.documents];
+ return _documents == otherMapping.documents;
}
- (NSUInteger)hash {
- return self.documents.hash;
+ return Hash(_documents);
}
- (void)addDocumentKey:(const DocumentKey &)documentKey {
- self.documents = [self.documents setByAddingObject:documentKey];
+ _documents = _documents.insert(documentKey);
}
- (void)removeDocumentKey:(const DocumentKey &)documentKey {
- self.documents = [self.documents setByRemovingObject:documentKey];
+ _documents = _documents.erase(documentKey);
+}
+
+- (void)filterUpdatesUsingExistingKeys:(const DocumentKeySet &)existingKeys {
+ // No-op. Resets are not filtered.
}
@end
#pragma mark - FSTUpdateMapping
-@interface FSTUpdateMapping ()
-@property(nonatomic, strong) FSTDocumentKeySet *addedDocuments;
-@property(nonatomic, strong) FSTDocumentKeySet *removedDocuments;
-@end
-
-@implementation FSTUpdateMapping
+@implementation FSTUpdateMapping {
+ DocumentKeySet _addedDocuments;
+ DocumentKeySet _removedDocuments;
+}
+ (FSTUpdateMapping *)mappingWithAddedDocuments:(NSArray<FSTDocument *> *)added
removedDocuments:(NSArray<FSTDocument *> *)removed {
- FSTUpdateMapping *mapping = [[FSTUpdateMapping alloc] init];
+ DocumentKeySet addedDocuments;
+ DocumentKeySet removedDocuments;
for (FSTDocument *doc in added) {
- mapping.addedDocuments = [mapping.addedDocuments setByAddingObject:doc.key];
+ addedDocuments = addedDocuments.insert(doc.key);
}
for (FSTDocument *doc in removed) {
- mapping.removedDocuments = [mapping.removedDocuments setByAddingObject:doc.key];
+ removedDocuments = removedDocuments.insert(doc.key);
}
- return mapping;
+ return [[FSTUpdateMapping alloc] initWithAddedDocuments:std::move(addedDocuments)
+ removedDocuments:std::move(removedDocuments)];
}
-- (instancetype)init {
+- (instancetype)initWithAddedDocuments:(DocumentKeySet)addedDocuments
+ removedDocuments:(DocumentKeySet)removedDocuments {
self = [super init];
if (self) {
- _addedDocuments = [FSTDocumentKeySet keySet];
- _removedDocuments = [FSTDocumentKeySet keySet];
+ _addedDocuments = std::move(addedDocuments);
+ _removedDocuments = std::move(removedDocuments);
}
return self;
}
+- (const DocumentKeySet &)addedDocuments {
+ return _addedDocuments;
+}
+
+- (const DocumentKeySet &)removedDocuments {
+ return _removedDocuments;
+}
+
- (BOOL)isEqual:(id)other {
if (other == self) {
return YES;
@@ -145,33 +167,43 @@ NS_ASSUME_NONNULL_BEGIN
}
FSTUpdateMapping *otherMapping = (FSTUpdateMapping *)other;
- return [self.addedDocuments isEqual:otherMapping.addedDocuments] &&
- [self.removedDocuments isEqual:otherMapping.removedDocuments];
+ return _addedDocuments == otherMapping.addedDocuments &&
+ _removedDocuments == otherMapping.removedDocuments;
}
- (NSUInteger)hash {
- return self.addedDocuments.hash * 31 + self.removedDocuments.hash;
+ return Hash(_addedDocuments, _removedDocuments);
}
-- (FSTDocumentKeySet *)applyTo:(FSTDocumentKeySet *)keys {
- __block FSTDocumentKeySet *result = keys;
- [self.addedDocuments enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
- result = [result setByAddingObject:key];
- }];
- [self.removedDocuments enumerateObjectsUsingBlock:^(FSTDocumentKey *key, BOOL *stop) {
- result = [result setByRemovingObject:key];
- }];
+- (DocumentKeySet)applyTo:(const DocumentKeySet &)keys {
+ DocumentKeySet result = keys;
+ for (const DocumentKey &key : _addedDocuments) {
+ result = result.insert(key);
+ }
+ for (const DocumentKey &key : _removedDocuments) {
+ result = result.erase(key);
+ }
return result;
}
- (void)addDocumentKey:(const DocumentKey &)documentKey {
- self.addedDocuments = [self.addedDocuments setByAddingObject:documentKey];
- self.removedDocuments = [self.removedDocuments setByRemovingObject:documentKey];
+ _addedDocuments = _addedDocuments.insert(documentKey);
+ _removedDocuments = _removedDocuments.erase(documentKey);
}
- (void)removeDocumentKey:(const DocumentKey &)documentKey {
- self.addedDocuments = [self.addedDocuments setByRemovingObject:documentKey];
- self.removedDocuments = [self.removedDocuments setByAddingObject:documentKey];
+ _addedDocuments = _addedDocuments.erase(documentKey);
+ _removedDocuments = _removedDocuments.insert(documentKey);
+}
+
+- (void)filterUpdatesUsingExistingKeys:(const DocumentKeySet &)existingKeys {
+ DocumentKeySet result = _addedDocuments;
+ for (const DocumentKey &key : _addedDocuments) {
+ if (existingKeys.contains(key)) {
+ result = result.erase(key);
+ }
+ }
+ _addedDocuments = result;
}
@end
@@ -181,11 +213,12 @@ NS_ASSUME_NONNULL_BEGIN
@interface FSTTargetChange ()
@property(nonatomic, assign) FSTCurrentStatusUpdate currentStatusUpdate;
@property(nonatomic, strong, nullable) FSTTargetMapping *mapping;
-@property(nonatomic, strong) FSTSnapshotVersion *snapshotVersion;
@property(nonatomic, strong) NSData *resumeToken;
@end
-@implementation FSTTargetChange
+@implementation FSTTargetChange {
+ SnapshotVersion _snapshotVersion;
+}
- (instancetype)init {
if (self = [super init]) {
@@ -195,16 +228,32 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
+- (instancetype)initWithSnapshotVersion:(SnapshotVersion)snapshotVersion {
+ if (self = [self init]) {
+ _snapshotVersion = std::move(snapshotVersion);
+ }
+ return self;
+}
+
+- (const SnapshotVersion &)snapshotVersion {
+ return _snapshotVersion;
+}
+
+ (instancetype)changeWithDocuments:(NSArray<FSTMaybeDocument *> *)docs
currentStatusUpdate:(FSTCurrentStatusUpdate)currentStatusUpdate {
- FSTUpdateMapping *mapping = [[FSTUpdateMapping alloc] init];
+ DocumentKeySet addedDocuments;
+ DocumentKeySet removedDocuments;
for (FSTMaybeDocument *doc in docs) {
if ([doc isKindOfClass:[FSTDeletedDocument class]]) {
- mapping.removedDocuments = [mapping.removedDocuments setByAddingObject:doc.key];
+ removedDocuments = removedDocuments.insert(doc.key);
} else {
- mapping.addedDocuments = [mapping.addedDocuments setByAddingObject:doc.key];
+ addedDocuments = addedDocuments.insert(doc.key);
}
}
+ FSTUpdateMapping *mapping =
+ [[FSTUpdateMapping alloc] initWithAddedDocuments:std::move(addedDocuments)
+ removedDocuments:std::move(removedDocuments)];
+
FSTTargetChange *change = [[FSTTargetChange alloc] init];
change.mapping = mapping;
change.currentStatusUpdate = currentStatusUpdate;
@@ -212,11 +261,11 @@ NS_ASSUME_NONNULL_BEGIN
}
+ (instancetype)changeWithMapping:(FSTTargetMapping *)mapping
- snapshotVersion:(FSTSnapshotVersion *)snapshotVersion
+ snapshotVersion:(SnapshotVersion)snapshotVersion
currentStatusUpdate:(FSTCurrentStatusUpdate)currentStatusUpdate {
FSTTargetChange *change = [[FSTTargetChange alloc] init];
change.mapping = mapping;
- change.snapshotVersion = snapshotVersion;
+ change->_snapshotVersion = std::move(snapshotVersion);
change.currentStatusUpdate = currentStatusUpdate;
return change;
}
@@ -243,57 +292,42 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - FSTRemoteEvent
-@interface FSTRemoteEvent () {
- NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *_targetChanges;
-}
-
-- (instancetype)
-initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
- targetChanges:(NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges
- documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates;
-
-@property(nonatomic, strong) FSTSnapshotVersion *snapshotVersion;
-
-@end
-
@implementation FSTRemoteEvent {
+ SnapshotVersion _snapshotVersion;
+ NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *_targetChanges;
std::map<DocumentKey, FSTMaybeDocument *> _documentUpdates;
-}
-+ (instancetype)
-eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
- targetChanges:(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
- documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates {
- return [[FSTRemoteEvent alloc] initWithSnapshotVersion:snapshotVersion
- targetChanges:targetChanges
- documentUpdates:std::move(documentUpdates)];
+ DocumentKeySet _limboDocumentChanges;
}
-- (instancetype)initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
+- (instancetype)initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
targetChanges:
(NSMutableDictionary<NSNumber *, FSTTargetChange *> *)targetChanges
- documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates {
+ documentUpdates:(std::map<DocumentKey, FSTMaybeDocument *>)documentUpdates
+ limboDocuments:(DocumentKeySet)limboDocuments {
self = [super init];
if (self) {
- _snapshotVersion = snapshotVersion;
+ _snapshotVersion = std::move(snapshotVersion);
_targetChanges = targetChanges;
_documentUpdates = std::move(documentUpdates);
+ _limboDocumentChanges = std::move(limboDocuments);
}
return self;
}
-- (void)filterUpdatesFromTargetChange:(FSTTargetChange *)targetChange
- existingDocuments:(FSTDocumentKeySet *)existingDocuments {
- if ([targetChange.mapping isKindOfClass:[FSTUpdateMapping class]]) {
- FSTUpdateMapping *update = (FSTUpdateMapping *)targetChange.mapping;
- FSTDocumentKeySet *added = update.addedDocuments;
- __block FSTDocumentKeySet *result = added;
- [added enumerateObjectsUsingBlock:^(FSTDocumentKey *docKey, BOOL *stop) {
- if ([existingDocuments containsObject:docKey]) {
- result = [result setByRemovingObject:docKey];
- }
- }];
- update.addedDocuments = result;
- }
+- (NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges {
+ return _targetChanges;
+}
+
+- (const DocumentKeySet &)limboDocumentChanges {
+ return _limboDocumentChanges;
+}
+
+- (const std::map<DocumentKey, FSTMaybeDocument *> &)documentUpdates {
+ return _documentUpdates;
+}
+
+- (const SnapshotVersion &)snapshotVersion {
+ return _snapshotVersion;
}
- (void)synthesizeDeleteForLimboTargetChange:(FSTTargetChange *)targetChange
@@ -314,22 +348,15 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
// However, if the document doesn't exist and the current marker arrives, the document is
// not present in the snapshot and our normal view handling would consider the document to
// remain in limbo indefinitely because there are no updates to the document. To avoid this,
- // we specially handle this just this case here: synthesizing a delete.
+ // we specially handle this case here: synthesizing a delete.
//
// TODO(dimond): Ideally we would have an explicit lookup query instead resulting in an
// explicit delete message and we could remove this special logic.
_documentUpdates[key] = [FSTDeletedDocument documentWithKey:key version:_snapshotVersion];
+ _limboDocumentChanges = _limboDocumentChanges.insert(key);
}
}
-- (NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *)targetChanges {
- return static_cast<NSDictionary<FSTBoxedTargetID *, FSTTargetChange *> *>(_targetChanges);
-}
-
-- (const std::map<DocumentKey, FSTMaybeDocument *> &)documentUpdates {
- return _documentUpdates;
-}
-
/** Adds a document update to this remote event */
- (void)addDocumentUpdate:(FSTMaybeDocument *)document {
_documentUpdates[document.key] = document;
@@ -350,7 +377,7 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
// TODO(dimond): keep track of reset targets not to raise.
FSTTargetChange *targetChange =
[FSTTargetChange changeWithMapping:[[FSTResetMapping alloc] init]
- snapshotVersion:[FSTSnapshotVersion noVersion]
+ snapshotVersion:SnapshotVersion::None()
currentStatusUpdate:FSTCurrentStatusUpdateMarkNotCurrent];
_targetChanges[targetID] = targetChange;
}
@@ -361,9 +388,6 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
@interface FSTWatchChangeAggregator ()
-/** The snapshot version for every target change this creates. */
-@property(nonatomic, strong, readonly) FSTSnapshotVersion *snapshotVersion;
-
/** Keeps track of the current target mappings */
@property(nonatomic, strong, readonly)
NSMutableDictionary<FSTBoxedTargetID *, FSTTargetChange *> *targetChanges;
@@ -381,35 +405,38 @@ eventWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
NSMutableDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *_existenceFilters;
/** Keeps track of document to update */
std::map<DocumentKey, FSTMaybeDocument *> _documentUpdates;
+
+ DocumentKeySet _limboDocuments;
+ /** The snapshot version for every target change this creates. */
+ SnapshotVersion _snapshotVersion;
}
- (instancetype)
-initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
+initWithSnapshotVersion:(SnapshotVersion)snapshotVersion
listenTargets:(NSDictionary<FSTBoxedTargetID *, FSTQueryData *> *)listenTargets
pendingTargetResponses:(NSDictionary<FSTBoxedTargetID *, NSNumber *> *)pendingTargetResponses {
self = [super init];
if (self) {
- _snapshotVersion = snapshotVersion;
+ _snapshotVersion = std::move(snapshotVersion);
_frozen = NO;
_targetChanges = [NSMutableDictionary dictionary];
_listenTargets = listenTargets;
_pendingTargetResponses = [NSMutableDictionary dictionaryWithDictionary:pendingTargetResponses];
-
+ _limboDocuments = DocumentKeySet{};
_existenceFilters = [NSMutableDictionary dictionary];
}
return self;
}
- (NSDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *)existenceFilters {
- return static_cast<NSDictionary<FSTBoxedTargetID *, FSTExistenceFilter *> *>(_existenceFilters);
+ return _existenceFilters;
}
- (FSTTargetChange *)targetChangeForTargetID:(FSTBoxedTargetID *)targetID {
FSTTargetChange *change = self.targetChanges[targetID];
if (!change) {
- change = [[FSTTargetChange alloc] init];
- change.snapshotVersion = self.snapshotVersion;
+ change = [[FSTTargetChange alloc] initWithSnapshotVersion:_snapshotVersion];
self.targetChanges[targetID] = change;
}
return change;
@@ -435,20 +462,66 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
}
}
+/**
+ * Updates limbo document tracking for a given target-document mapping change. If the target is a
+ * limbo target, and the change for the document has only seen limbo targets so far, and we are not
+ * already tracking a change for this document, then consider this document a limbo document update.
+ * Otherwise, ensure that we don't consider this document a limbo document. Returns true if the
+ * change still has only seen limbo resolution changes.
+ */
+- (BOOL)updateLimboDocumentsForKey:(const DocumentKey &)documentKey
+ queryData:(FSTQueryData *)queryData
+ isOnlyLimbo:(BOOL)isOnlyLimbo {
+ if (!isOnlyLimbo) {
+ // It wasn't a limbo doc before, so it definitely isn't now.
+ return NO;
+ }
+ if (_documentUpdates.find(documentKey) == _documentUpdates.end()) {
+ // We haven't seen a document update for this key yet.
+ if (queryData.purpose == FSTQueryPurposeLimboResolution) {
+ // We haven't seen this document before, and this target is a limbo target.
+ _limboDocuments = _limboDocuments.insert(documentKey);
+ return YES;
+ } else {
+ // We haven't seen the document before, but this is a non-limbo target.
+ // Since we haven't seen it, we know it's not in our set of limbo docs. Return NO to ensure
+ // that this key is marked as non-limbo.
+ return NO;
+ }
+ } else if (queryData.purpose == FSTQueryPurposeLimboResolution) {
+ // We have only seen limbo targets so far for this document, and this is another limbo target.
+ return YES;
+ } else {
+ // We haven't marked this as non-limbo yet, but this target is not a limbo target.
+ // Mark the key as non-limbo and make sure it isn't in our set.
+ _limboDocuments = _limboDocuments.erase(documentKey);
+ return NO;
+ }
+}
+
- (void)addDocumentChange:(FSTDocumentWatchChange *)docChange {
BOOL relevant = NO;
+ BOOL isOnlyLimbo = YES;
for (FSTBoxedTargetID *targetID in docChange.updatedTargetIDs) {
- if ([self isActiveTarget:targetID]) {
+ FSTQueryData *queryData = [self queryDataForActiveTarget:targetID];
+ if (queryData) {
FSTTargetChange *change = [self targetChangeForTargetID:targetID];
+ isOnlyLimbo = [self updateLimboDocumentsForKey:docChange.documentKey
+ queryData:queryData
+ isOnlyLimbo:isOnlyLimbo];
[change.mapping addDocumentKey:docChange.documentKey];
relevant = YES;
}
}
for (FSTBoxedTargetID *targetID in docChange.removedTargetIDs) {
- if ([self isActiveTarget:targetID]) {
+ FSTQueryData *queryData = [self queryDataForActiveTarget:targetID];
+ if (queryData) {
FSTTargetChange *change = [self targetChangeForTargetID:targetID];
+ isOnlyLimbo = [self updateLimboDocumentsForKey:docChange.documentKey
+ queryData:queryData
+ isOnlyLimbo:isOnlyLimbo];
[change.mapping removeDocumentKey:docChange.documentKey];
relevant = YES;
}
@@ -473,7 +546,7 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
break;
case FSTWatchTargetChangeStateAdded:
[self recordResponseForTargetID:targetID];
- if (![self.pendingTargetResponses objectForKey:targetID]) {
+ if (!self.pendingTargetResponses[targetID]) {
// We have a freshly added target, so we need to reset any state that we had previously
// This can happen e.g. when remove and add back a target for existence filter
// mismatches.
@@ -514,12 +587,12 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
* responses that we have.
*/
- (void)recordResponseForTargetID:(FSTBoxedTargetID *)targetID {
- NSNumber *count = [self.pendingTargetResponses objectForKey:targetID];
+ NSNumber *count = self.pendingTargetResponses[targetID];
int newCount = count ? [count intValue] - 1 : -1;
if (newCount == 0) {
[self.pendingTargetResponses removeObjectForKey:targetID];
} else {
- [self.pendingTargetResponses setObject:[NSNumber numberWithInt:newCount] forKey:targetID];
+ self.pendingTargetResponses[targetID] = @(newCount);
}
}
@@ -532,8 +605,12 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
* yet acknowledged the intended change in state.
*/
- (BOOL)isActiveTarget:(FSTBoxedTargetID *)targetID {
- return [self.listenTargets objectForKey:targetID] &&
- ![self.pendingTargetResponses objectForKey:targetID];
+ return [self queryDataForActiveTarget:targetID] != nil;
+}
+
+- (FSTQueryData *_Nullable)queryDataForActiveTarget:(FSTBoxedTargetID *)targetID {
+ FSTQueryData *queryData = self.listenTargets[targetID];
+ return (queryData && !self.pendingTargetResponses[targetID]) ? queryData : nil;
}
- (void)addExistenceFilterChange:(FSTExistenceFilterWatchChange *)existenceFilterChange {
@@ -559,9 +636,10 @@ initWithSnapshotVersion:(FSTSnapshotVersion *)snapshotVersion
// Mark this aggregator as frozen so no further modifications are made.
self.frozen = YES;
- return [FSTRemoteEvent eventWithSnapshotVersion:self.snapshotVersion
- targetChanges:targetChanges
- documentUpdates:_documentUpdates];
+ return [[FSTRemoteEvent alloc] initWithSnapshotVersion:_snapshotVersion
+ targetChanges:targetChanges
+ documentUpdates:_documentUpdates
+ limboDocuments:_limboDocuments];
}
@end
diff --git a/Firestore/Source/Remote/FSTRemoteStore.h b/Firestore/Source/Remote/FSTRemoteStore.h
index 09e1d32..9b01ce4 100644
--- a/Firestore/Source/Remote/FSTRemoteStore.h
+++ b/Firestore/Source/Remote/FSTRemoteStore.h
@@ -17,7 +17,6 @@
#import <Foundation/Foundation.h>
#import "Firestore/Source/Core/FSTTypes.h"
-#import "Firestore/Source/Model/FSTDocumentVersionDictionary.h"
#include "Firestore/core/src/firebase/firestore/auth/user.h"
diff --git a/Firestore/Source/Remote/FSTRemoteStore.mm b/Firestore/Source/Remote/FSTRemoteStore.mm
index 39d285a..0ea4887 100644
--- a/Firestore/Source/Remote/FSTRemoteStore.mm
+++ b/Firestore/Source/Remote/FSTRemoteStore.mm
@@ -19,7 +19,6 @@
#include <cinttypes>
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Core/FSTTransaction.h"
#import "Firestore/Source/Local/FSTLocalStore.h"
#import "Firestore/Source/Local/FSTQueryData.h"
@@ -37,11 +36,14 @@
#include "Firestore/core/src/firebase/firestore/auth/user.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
namespace util = firebase::firestore::util;
using firebase::firestore::auth::User;
using firebase::firestore::model::DocumentKey;
+using firebase::firestore::model::SnapshotVersion;
+using firebase::firestore::model::DocumentKeySet;
NS_ASSUME_NONNULL_BEGIN
@@ -299,7 +301,7 @@ static const int kMaxPendingWrites = 10;
}
- (void)watchStreamDidChange:(FSTWatchChange *)change
- snapshotVersion:(FSTSnapshotVersion *)snapshotVersion {
+ snapshotVersion:(const SnapshotVersion &)snapshotVersion {
// Mark the connection as Online because we got a message from the server.
[self.onlineStateTracker updateState:FSTOnlineStateOnline];
@@ -315,10 +317,8 @@ static const int kMaxPendingWrites = 10;
// older than a previous snapshot we've processed (can happen after we resume a target
// using a resume token).
[self.accumulatedChanges addObject:change];
- FSTAssert(snapshotVersion, @"snapshotVersion must not be nil.");
- if ([snapshotVersion isEqual:[FSTSnapshotVersion noVersion]] ||
- [snapshotVersion compare:[self.localStore lastRemoteSnapshotVersion]] ==
- NSOrderedAscending) {
+ if (snapshotVersion == SnapshotVersion::None() ||
+ snapshotVersion < [self.localStore lastRemoteSnapshotVersion]) {
return;
}
@@ -354,7 +354,7 @@ static const int kMaxPendingWrites = 10;
* on to the SyncEngine.
*/
- (void)processBatchedWatchChanges:(NSArray<FSTWatchChange *> *)changes
- snapshotVersion:(FSTSnapshotVersion *)snapshotVersion {
+ snapshotVersion:(const SnapshotVersion &)snapshotVersion {
FSTWatchChangeAggregator *aggregator =
[[FSTWatchChangeAggregator alloc] initWithSnapshotVersion:snapshotVersion
listenTargets:self.listenTargets
@@ -394,7 +394,7 @@ static const int kMaxPendingWrites = 10;
} else {
// Not a document query.
- FSTDocumentKeySet *trackedRemote = [self.localStore remoteDocumentKeysForTarget:targetID];
+ DocumentKeySet trackedRemote = [self.localStore remoteDocumentKeysForTarget:targetID];
FSTTargetMapping *mapping = remoteEvent.targetChanges[target].mapping;
if (mapping) {
if ([mapping isKindOfClass:[FSTUpdateMapping class]]) {
@@ -407,7 +407,7 @@ static const int kMaxPendingWrites = 10;
}
}
- if (trackedRemote.count != (NSUInteger)filter.count) {
+ if (trackedRemote.size() != static_cast<size_t>(filter.count)) {
FSTLog(@"Existence filter mismatch, resetting mapping");
// Make sure the mismatch is exposed in the remote event
@@ -449,7 +449,8 @@ static const int kMaxPendingWrites = 10;
if (queryData) {
self->_listenTargets[target] =
[queryData queryDataByReplacingSnapshotVersion:change.snapshotVersion
- resumeToken:resumeToken];
+ resumeToken:resumeToken
+ sequenceNumber:queryData.sequenceNumber];
}
}
}];
@@ -566,7 +567,7 @@ static const int kMaxPendingWrites = 10;
}
/** Handles a successful StreamingWriteResponse from the server that contains a mutation result. */
-- (void)writeStreamDidReceiveResponseWithVersion:(FSTSnapshotVersion *)commitVersion
+- (void)writeStreamDidReceiveResponseWithVersion:(const SnapshotVersion &)commitVersion
mutationResults:(NSArray<FSTMutationResult *> *)results {
// This is a response to a write containing mutations and should be correlated to the first
// pending write.
diff --git a/Firestore/Source/Remote/FSTSerializerBeta.h b/Firestore/Source/Remote/FSTSerializerBeta.h
index d96dbeb..cdf5d1f 100644
--- a/Firestore/Source/Remote/FSTSerializerBeta.h
+++ b/Firestore/Source/Remote/FSTSerializerBeta.h
@@ -16,8 +16,10 @@
#import <Foundation/Foundation.h>
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
#include "Firestore/core/src/firebase/firestore/model/database_id.h"
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
@class FSTFieldValue;
@class FSTMaybeDocument;
@@ -27,8 +29,6 @@
@class FSTObjectValue;
@class FSTQuery;
@class FSTQueryData;
-@class FSTSnapshotVersion;
-@class FIRTimestamp;
@class FSTWatchChange;
@class GCFSBatchGetDocumentsResponse;
@@ -61,15 +61,19 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithDatabaseID:(const firebase::firestore::model::DatabaseId *)databaseID
NS_DESIGNATED_INITIALIZER;
-- (GPBTimestamp *)encodedTimestamp:(FIRTimestamp *)timestamp;
-- (FIRTimestamp *)decodedTimestamp:(GPBTimestamp *)timestamp;
+- (GPBTimestamp *)encodedTimestamp:(const firebase::Timestamp &)timestamp;
+- (firebase::Timestamp)decodedTimestamp:(GPBTimestamp *)timestamp;
-- (GPBTimestamp *)encodedVersion:(FSTSnapshotVersion *)version;
-- (FSTSnapshotVersion *)decodedVersion:(GPBTimestamp *)version;
+- (GPBTimestamp *)encodedVersion:(const firebase::firestore::model::SnapshotVersion &)version;
+- (firebase::firestore::model::SnapshotVersion)decodedVersion:(GPBTimestamp *)version;
/** Returns the database ID, such as `projects/{project id}/databases/{database_id}`. */
- (NSString *)encodedDatabaseID;
+/**
+ * Encodes the given document key as a fully qualified name. This includes the
+ * databaseId associated with this FSTSerializerBeta and the key path.
+ */
- (NSString *)encodedDocumentKey:(const firebase::firestore::model::DocumentKey &)key;
- (firebase::firestore::model::DocumentKey)decodedDocumentKey:(NSString *)key;
@@ -93,7 +97,8 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTQuery *)decodedQueryFromQueryTarget:(GCFSTarget_QueryTarget *)target;
- (FSTWatchChange *)decodedWatchChange:(GCFSListenResponse *)watchChange;
-- (FSTSnapshotVersion *)versionFromListenResponse:(GCFSListenResponse *)watchChange;
+- (firebase::firestore::model::SnapshotVersion)versionFromListenResponse:
+ (GCFSListenResponse *)watchChange;
- (GCFSDocument *)encodedDocumentWithFields:(FSTObjectValue *)objectValue
key:(const firebase::firestore::model::DocumentKey &)key;
diff --git a/Firestore/Source/Remote/FSTSerializerBeta.mm b/Firestore/Source/Remote/FSTSerializerBeta.mm
index 5cbfecc..f862ec3 100644
--- a/Firestore/Source/Remote/FSTSerializerBeta.mm
+++ b/Firestore/Source/Remote/FSTSerializerBeta.mm
@@ -33,9 +33,7 @@
#import "FIRFirestoreErrors.h"
#import "FIRGeoPoint.h"
-#import "FIRTimestamp.h"
#import "Firestore/Source/Core/FSTQuery.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Local/FSTQueryData.h"
#import "Firestore/Source/Model/FSTDocument.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
@@ -55,8 +53,10 @@
#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
#include "absl/memory/memory.h"
+#include "absl/types/optional.h"
namespace util = firebase::firestore::util;
+using firebase::Timestamp;
using firebase::firestore::model::ArrayTransform;
using firebase::firestore::model::DatabaseId;
using firebase::firestore::model::DocumentKey;
@@ -86,25 +86,25 @@ NS_ASSUME_NONNULL_BEGIN
return self;
}
-#pragma mark - FSTSnapshotVersion <=> GPBTimestamp
+#pragma mark - SnapshotVersion <=> GPBTimestamp
-- (GPBTimestamp *)encodedTimestamp:(FIRTimestamp *)timestamp {
+- (GPBTimestamp *)encodedTimestamp:(const Timestamp &)timestamp {
GPBTimestamp *result = [GPBTimestamp message];
- result.seconds = timestamp.seconds;
- result.nanos = timestamp.nanoseconds;
+ result.seconds = timestamp.seconds();
+ result.nanos = timestamp.nanoseconds();
return result;
}
-- (FIRTimestamp *)decodedTimestamp:(GPBTimestamp *)timestamp {
- return [[FIRTimestamp alloc] initWithSeconds:timestamp.seconds nanoseconds:timestamp.nanos];
+- (Timestamp)decodedTimestamp:(GPBTimestamp *)timestamp {
+ return Timestamp{timestamp.seconds, timestamp.nanos};
}
-- (GPBTimestamp *)encodedVersion:(FSTSnapshotVersion *)version {
- return [self encodedTimestamp:version.timestamp];
+- (GPBTimestamp *)encodedVersion:(const SnapshotVersion &)version {
+ return [self encodedTimestamp:version.timestamp()];
}
-- (FSTSnapshotVersion *)decodedVersion:(GPBTimestamp *)version {
- return [FSTSnapshotVersion versionWithTimestamp:[self decodedTimestamp:version]];
+- (SnapshotVersion)decodedVersion:(GPBTimestamp *)version {
+ return SnapshotVersion{[self decodedTimestamp:version]};
}
#pragma mark - FIRGeoPoint <=> GTPLatLng
@@ -206,8 +206,8 @@ NS_ASSUME_NONNULL_BEGIN
return [self encodedString:[fieldValue value]];
} else if (fieldClass == [FSTTimestampValue class]) {
- return [self encodedTimestampValue:[fieldValue value]];
-
+ FIRTimestamp *value = static_cast<FIRTimestamp *>([fieldValue value]);
+ return [self encodedTimestampValue:Timestamp{value.seconds, value.nanoseconds}];
} else if (fieldClass == [FSTGeoPointValue class]) {
return [self encodedGeoPointValue:[fieldValue value]];
@@ -250,8 +250,12 @@ NS_ASSUME_NONNULL_BEGIN
case GCFSValue_ValueType_OneOfCase_StringValue:
return [FSTStringValue stringValue:valueProto.stringValue];
- case GCFSValue_ValueType_OneOfCase_TimestampValue:
- return [FSTTimestampValue timestampValue:[self decodedTimestamp:valueProto.timestampValue]];
+ case GCFSValue_ValueType_OneOfCase_TimestampValue: {
+ Timestamp value = [self decodedTimestamp:valueProto.timestampValue];
+ return [FSTTimestampValue
+ timestampValue:[FIRTimestamp timestampWithSeconds:value.seconds()
+ nanoseconds:value.nanoseconds()]];
+ }
case GCFSValue_ValueType_OneOfCase_GeoPointValue:
return [FSTGeoPointValue geoPointValue:[self decodedGeoPoint:valueProto.geoPointValue]];
@@ -303,7 +307,7 @@ NS_ASSUME_NONNULL_BEGIN
return result;
}
-- (GCFSValue *)encodedTimestampValue:(FIRTimestamp *)value {
+- (GCFSValue *)encodedTimestampValue:(const Timestamp &)value {
GCFSValue *result = [GCFSValue message];
result.timestampValue = [self encodedTimestamp:value];
return result;
@@ -429,8 +433,8 @@ NS_ASSUME_NONNULL_BEGIN
FSTAssert(!!response.found, @"Tried to deserialize a found document from a deleted document.");
const DocumentKey key = [self decodedDocumentKey:response.found.name];
FSTObjectValue *value = [self decodedFields:response.found.fields];
- FSTSnapshotVersion *version = [self decodedVersion:response.found.updateTime];
- FSTAssert(![version isEqual:[FSTSnapshotVersion noVersion]],
+ SnapshotVersion version = [self decodedVersion:response.found.updateTime];
+ FSTAssert(version != SnapshotVersion::None(),
@"Got a document response with no snapshot version");
return [FSTDocument documentWithData:value key:key version:version hasLocalMutations:NO];
@@ -439,8 +443,8 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTDeletedDocument *)decodedDeletedDocument:(GCFSBatchGetDocumentsResponse *)response {
FSTAssert(!!response.missing, @"Tried to deserialize a deleted document from a found document.");
const DocumentKey key = [self decodedDocumentKey:response.missing];
- FSTSnapshotVersion *version = [self decodedVersion:response.readTime];
- FSTAssert(![version isEqual:[FSTSnapshotVersion noVersion]],
+ SnapshotVersion version = [self decodedVersion:response.readTime];
+ FSTAssert(version != SnapshotVersion::None(),
@"Got a no document response with no snapshot version");
return [FSTDeletedDocument documentWithKey:key version:version];
}
@@ -668,8 +672,10 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTMutationResult *)decodedMutationResult:(GCFSWriteResult *)mutation {
// NOTE: Deletes don't have an updateTime.
- FSTSnapshotVersion *_Nullable version =
- mutation.updateTime ? [self decodedVersion:mutation.updateTime] : nil;
+ absl::optional<SnapshotVersion> version;
+ if (mutation.updateTime) {
+ version = [self decodedVersion:mutation.updateTime];
+ }
NSMutableArray *_Nullable transformResults = nil;
if (mutation.transformResultsArray.count > 0) {
transformResults = [NSMutableArray array];
@@ -677,7 +683,8 @@ NS_ASSUME_NONNULL_BEGIN
[transformResults addObject:[self decodedFieldValue:result]];
}
}
- return [[FSTMutationResult alloc] initWithVersion:version transformResults:transformResults];
+ return [[FSTMutationResult alloc] initWithVersion:std::move(version)
+ transformResults:transformResults];
}
#pragma mark - FSTQueryData => GCFSTarget proto
@@ -1071,15 +1078,15 @@ NS_ASSUME_NONNULL_BEGIN
}
}
-- (FSTSnapshotVersion *)versionFromListenResponse:(GCFSListenResponse *)watchChange {
+- (SnapshotVersion)versionFromListenResponse:(GCFSListenResponse *)watchChange {
// We have only reached a consistent snapshot for the entire stream if there is a read_time set
// and it applies to all targets (i.e. the list of targets is empty). The backend is guaranteed to
// send such responses.
if (watchChange.responseTypeOneOfCase != GCFSListenResponse_ResponseType_OneOfCase_TargetChange) {
- return [FSTSnapshotVersion noVersion];
+ return SnapshotVersion::None();
}
if (watchChange.targetChange.targetIdsArray.count != 0) {
- return [FSTSnapshotVersion noVersion];
+ return SnapshotVersion::None();
}
return [self decodedVersion:watchChange.targetChange.readTime];
}
@@ -1135,9 +1142,8 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTDocumentWatchChange *)decodedDocumentChange:(GCFSDocumentChange *)change {
FSTObjectValue *value = [self decodedFields:change.document.fields];
const DocumentKey key = [self decodedDocumentKey:change.document.name];
- FSTSnapshotVersion *version = [self decodedVersion:change.document.updateTime];
- FSTAssert(![version isEqual:[FSTSnapshotVersion noVersion]],
- @"Got a document change with no snapshot version");
+ SnapshotVersion version = [self decodedVersion:change.document.updateTime];
+ FSTAssert(version != SnapshotVersion::None(), @"Got a document change with no snapshot version");
FSTMaybeDocument *document =
[FSTDocument documentWithData:value key:key version:version hasLocalMutations:NO];
@@ -1152,8 +1158,8 @@ NS_ASSUME_NONNULL_BEGIN
- (FSTDocumentWatchChange *)decodedDocumentDelete:(GCFSDocumentDelete *)change {
const DocumentKey key = [self decodedDocumentKey:change.document];
- // Note that version might be unset in which case we use [FSTSnapshotVersion noVersion]
- FSTSnapshotVersion *version = [self decodedVersion:change.readTime];
+ // Note that version might be unset in which case we use SnapshotVersion::None()
+ SnapshotVersion version = [self decodedVersion:change.readTime];
FSTMaybeDocument *document = [FSTDeletedDocument documentWithKey:key version:version];
NSArray<NSNumber *> *removedTargetIds = [self decodedIntegerArray:change.removedTargetIdsArray];
diff --git a/Firestore/Source/Remote/FSTStream.h b/Firestore/Source/Remote/FSTStream.h
index fba79d2..3bd8549 100644
--- a/Firestore/Source/Remote/FSTStream.h
+++ b/Firestore/Source/Remote/FSTStream.h
@@ -21,13 +21,13 @@
#include "Firestore/core/src/firebase/firestore/auth/credentials_provider.h"
#include "Firestore/core/src/firebase/firestore/core/database_info.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
@class FSTDispatchQueue;
@class FSTMutation;
@class FSTMutationResult;
@class FSTQueryData;
@class FSTSerializerBeta;
-@class FSTSnapshotVersion;
@class FSTWatchChange;
@class FSTWatchStream;
@class FSTWriteStream;
@@ -179,7 +179,7 @@ NS_ASSUME_NONNULL_BEGIN
* WatchChange responses sent back by the server.
*/
- (void)watchStreamDidChange:(FSTWatchChange *)change
- snapshotVersion:(FSTSnapshotVersion *)snapshotVersion;
+ snapshotVersion:(const firebase::firestore::model::SnapshotVersion &)snapshotVersion;
/**
* Called by the FSTWatchStream when the underlying streaming RPC is interrupted for whatever
@@ -250,7 +250,8 @@ NS_ASSUME_NONNULL_BEGIN
* Called by the FSTWriteStream upon receiving a StreamingWriteResponse from the server that
* contains mutation results.
*/
-- (void)writeStreamDidReceiveResponseWithVersion:(FSTSnapshotVersion *)commitVersion
+- (void)writeStreamDidReceiveResponseWithVersion:
+ (const firebase::firestore::model::SnapshotVersion &)commitVersion
mutationResults:(NSArray<FSTMutationResult *> *)results;
/**
diff --git a/Firestore/Source/Remote/FSTStream.mm b/Firestore/Source/Remote/FSTStream.mm
index a96feae..f4ec675 100644
--- a/Firestore/Source/Remote/FSTStream.mm
+++ b/Firestore/Source/Remote/FSTStream.mm
@@ -36,6 +36,7 @@
#include "Firestore/core/src/firebase/firestore/auth/token.h"
#include "Firestore/core/src/firebase/firestore/core/database_info.h"
#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
#include "Firestore/core/src/firebase/firestore/util/error_apple.h"
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
@@ -44,6 +45,7 @@ using firebase::firestore::auth::CredentialsProvider;
using firebase::firestore::auth::Token;
using firebase::firestore::core::DatabaseInfo;
using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::SnapshotVersion;
/**
* Initial backoff time in seconds after an error.
@@ -691,7 +693,7 @@ static const NSTimeInterval kIdleTimeout = 60.0;
[self.backoff reset];
FSTWatchChange *change = [_serializer decodedWatchChange:proto];
- FSTSnapshotVersion *snap = [_serializer versionFromListenResponse:proto];
+ SnapshotVersion snap = [_serializer versionFromListenResponse:proto];
[self.delegate watchStreamDidChange:change snapshotVersion:snap];
}
@@ -807,7 +809,7 @@ static const NSTimeInterval kIdleTimeout = 60.0;
// might be causing an error we want to back off from.
[self.backoff reset];
- FSTSnapshotVersion *commitVersion = [_serializer decodedVersion:response.commitTime];
+ SnapshotVersion commitVersion = [_serializer decodedVersion:response.commitTime];
NSMutableArray<GCFSWriteResult *> *protos = response.writeResultsArray;
NSMutableArray<FSTMutationResult *> *results = [NSMutableArray arrayWithCapacity:protos.count];
for (GCFSWriteResult *proto in protos) {
diff --git a/Firestore/Source/Remote/FSTWatchChange.h b/Firestore/Source/Remote/FSTWatchChange.h
index 8f730de..ed80e1a 100644
--- a/Firestore/Source/Remote/FSTWatchChange.h
+++ b/Firestore/Source/Remote/FSTWatchChange.h
@@ -22,7 +22,6 @@
@class FSTExistenceFilter;
@class FSTMaybeDocument;
-@class FSTSnapshotVersion;
NS_ASSUME_NONNULL_BEGIN
diff --git a/Firestore/Source/Util/FSTAsyncQueryListener.h b/Firestore/Source/Util/FSTAsyncQueryListener.h
index 4888268..06471d8 100644
--- a/Firestore/Source/Util/FSTAsyncQueryListener.h
+++ b/Firestore/Source/Util/FSTAsyncQueryListener.h
@@ -18,6 +18,8 @@
#import "Firestore/Source/Core/FSTViewSnapshot.h"
+#include "Firestore/core/src/firebase/firestore/util/executor.h"
+
NS_ASSUME_NONNULL_BEGIN
@class FSTDispatchQueue;
@@ -28,9 +30,8 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface FSTAsyncQueryListener : NSObject
-- (instancetype)initWithDispatchQueue:(FSTDispatchQueue *)dispatchQueue
- snapshotHandler:(FSTViewSnapshotHandler)snapshotHandler
- NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithExecutor:(firebase::firestore::util::internal::Executor*)executor
+ snapshotHandler:(FSTViewSnapshotHandler)snapshotHandler NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
diff --git a/Firestore/Source/Util/FSTAsyncQueryListener.mm b/Firestore/Source/Util/FSTAsyncQueryListener.mm
index b72ac57..81dd41f 100644
--- a/Firestore/Source/Util/FSTAsyncQueryListener.mm
+++ b/Firestore/Source/Util/FSTAsyncQueryListener.mm
@@ -18,16 +18,18 @@
#import "Firestore/Source/Util/FSTDispatchQueue.h"
+using firebase::firestore::util::internal::Executor;
+
@implementation FSTAsyncQueryListener {
volatile BOOL _muted;
FSTViewSnapshotHandler _snapshotHandler;
- FSTDispatchQueue *_dispatchQueue;
+ Executor *_executor;
}
-- (instancetype)initWithDispatchQueue:(FSTDispatchQueue *)dispatchQueue
- snapshotHandler:(FSTViewSnapshotHandler)snapshotHandler {
+- (instancetype)initWithExecutor:(Executor *)executor
+ snapshotHandler:(FSTViewSnapshotHandler)snapshotHandler {
if (self = [super init]) {
- _dispatchQueue = dispatchQueue;
+ _executor = executor;
_snapshotHandler = snapshotHandler;
}
return self;
@@ -40,11 +42,11 @@
// users just want to turn on notifications "forever" and don't want to have
// to keep track of our handle to keep them going.
return ^(FSTViewSnapshot *_Nullable snapshot, NSError *_Nullable error) {
- [self->_dispatchQueue dispatchAsync:^{
+ self->_executor->Execute([self, snapshot, error] {
if (!self->_muted) {
self->_snapshotHandler(snapshot, error);
}
- }];
+ });
};
}
diff --git a/Firestore/Source/Util/FSTDispatchQueue.mm b/Firestore/Source/Util/FSTDispatchQueue.mm
index 0974359..01b2732 100644
--- a/Firestore/Source/Util/FSTDispatchQueue.mm
+++ b/Firestore/Source/Util/FSTDispatchQueue.mm
@@ -16,154 +16,66 @@
#import <Foundation/Foundation.h>
-#include <atomic>
+#include <memory>
+#include <utility>
#import "Firestore/Source/Util/FSTAssert.h"
#import "Firestore/Source/Util/FSTDispatchQueue.h"
-NS_ASSUME_NONNULL_BEGIN
-
-/**
- * removeDelayedCallback is used by FSTDelayedCallback and so we pre-declare it before the rest of
- * the FSTDispatchQueue private interface.
- */
-@interface FSTDispatchQueue ()
-- (void)removeDelayedCallback:(FSTDelayedCallback *)callback;
-@end
+#include "Firestore/core/src/firebase/firestore/util/async_queue.h"
+#include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h"
+#include "absl/memory/memory.h"
-#pragma mark - FSTDelayedCallback
-
-/**
- * Represents a callback scheduled to be run in the future on an FSTDispatchQueue.
- *
- * It is created via [FSTDelayedCallback createAndScheduleWithQueue].
- *
- * Supports cancellation (via cancel) and early execution (via skipDelay).
- */
-@interface FSTDelayedCallback ()
+using firebase::firestore::util::AsyncQueue;
+using firebase::firestore::util::DelayedOperation;
+using firebase::firestore::util::TimerId;
+using firebase::firestore::util::internal::Executor;
+using firebase::firestore::util::internal::ExecutorLibdispatch;
-@property(nonatomic, strong, readonly) FSTDispatchQueue *queue;
-@property(nonatomic, assign, readonly) FSTTimerID timerID;
-@property(nonatomic, assign, readonly) NSTimeInterval targetTime;
-@property(nonatomic, copy) void (^callback)();
-/** YES if the callback has been run or canceled. */
-@property(nonatomic, getter=isDone) BOOL done;
+NS_ASSUME_NONNULL_BEGIN
-/**
- * Creates and returns an FSTDelayedCallback that has been scheduled on the provided queue with the
- * provided delay.
- *
- * @param queue The FSTDispatchQueue to run the callback on.
- * @param timerID A FSTTimerID identifying the type of the delayed callback.
- * @param delay The delay before the callback should be scheduled.
- * @param callback The callback block to run.
- * @return The created FSTDelayedCallback instance.
- */
-+ (instancetype)createAndScheduleWithQueue:(FSTDispatchQueue *)queue
- timerID:(FSTTimerID)timerID
- delay:(NSTimeInterval)delay
- callback:(void (^)(void))callback;
+#pragma mark - FSTDelayedCallback
-/**
- * Queues the callback to run immediately (if it hasn't already been run or canceled).
- */
-- (void)skipDelay;
+@interface FSTDelayedCallback () {
+ DelayedOperation _impl;
+}
@end
@implementation FSTDelayedCallback
-- (instancetype)initWithQueue:(FSTDispatchQueue *)queue
- timerID:(FSTTimerID)timerID
- targetTime:(NSTimeInterval)targetTime
- callback:(void (^)(void))callback {
+- (instancetype)initWithImpl:(DelayedOperation &&)impl {
if (self = [super init]) {
- _queue = queue;
- _timerID = timerID;
- _targetTime = targetTime;
- _callback = callback;
- _done = NO;
+ _impl = std::move(impl);
}
return self;
}
-+ (instancetype)createAndScheduleWithQueue:(FSTDispatchQueue *)queue
- timerID:(FSTTimerID)timerID
- delay:(NSTimeInterval)delay
- callback:(void (^)(void))callback {
- NSTimeInterval targetTime = [[NSDate date] timeIntervalSince1970] + delay;
- FSTDelayedCallback *delayedCallback = [[FSTDelayedCallback alloc] initWithQueue:queue
- timerID:timerID
- targetTime:targetTime
- callback:callback];
- [delayedCallback startWithDelay:delay];
- return delayedCallback;
-}
-
-/**
- * Starts the timer. This is called immediately after construction by createAndScheduleWithQueue.
- */
-- (void)startWithDelay:(NSTimeInterval)delay {
- dispatch_time_t delayNs = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC));
- dispatch_after(delayNs, self.queue.queue, ^{
- [self.queue enterCheckedOperation:^{
- [self delayDidElapse];
- }];
- });
-}
-
-- (void)skipDelay {
- [self.queue dispatchAsyncAllowingSameQueue:^{
- [self delayDidElapse];
- }];
-}
-
- (void)cancel {
- [self.queue verifyIsCurrentQueue];
- if (!self.isDone) {
- // PORTING NOTE: There's no way to actually cancel the dispatched callback, but it'll be a no-op
- // since we set done to YES.
- [self markDone];
- }
-}
-
-- (void)delayDidElapse {
- [self.queue verifyIsCurrentQueue];
- if (!self.isDone) {
- [self markDone];
- self.callback();
- }
-}
-
-/**
- * Marks this delayed callback as done, and notifies the FSTDispatchQueue that it should be removed.
- */
-- (void)markDone {
- self.done = YES;
- [self.queue removeDelayedCallback:self];
+ _impl.Cancel();
}
@end
#pragma mark - FSTDispatchQueue
-@interface FSTDispatchQueue ()
-/**
- * Callbacks scheduled to be queued in the future. Callbacks are automatically removed after they
- * are run or canceled.
- */
-@property(nonatomic, strong, readonly) NSMutableArray<FSTDelayedCallback *> *delayedCallbacks;
-
-- (instancetype)initWithQueue:(dispatch_queue_t)queue NS_DESIGNATED_INITIALIZER;
-
-@end
-
@implementation FSTDispatchQueue {
- /**
- * Flag set while an FSTDispatchQueue operation is currently executing. Used for assertion
- * sanity-checks.
- */
- std::atomic<bool> _operationInProgress;
+ std::unique_ptr<AsyncQueue> _impl;
+}
+
++ (TimerId)convertTimerId:(FSTTimerID)objcTimerID {
+ const TimerId converted = static_cast<TimerId>(objcTimerID);
+ switch (converted) {
+ case TimerId::All:
+ case TimerId::ListenStreamIdle:
+ case TimerId::ListenStreamConnectionBackoff:
+ case TimerId::WriteStreamIdle:
+ case TimerId::WriteStreamConnectionBackoff:
+ case TimerId::OnlineStateTimeout:
+ return converted;
+ default:
+ FSTAssert(false, @"Unknown value of enum FSTTimerID.");
+ }
}
+ (instancetype)queueWith:(dispatch_queue_t)dispatchQueue {
@@ -172,141 +84,50 @@ NS_ASSUME_NONNULL_BEGIN
- (instancetype)initWithQueue:(dispatch_queue_t)queue {
if (self = [super init]) {
- _operationInProgress = false;
_queue = queue;
- _delayedCallbacks = [NSMutableArray array];
+ auto executor = absl::make_unique<ExecutorLibdispatch>(queue);
+ _impl = absl::make_unique<AsyncQueue>(std::move(executor));
}
return self;
}
- (void)verifyIsCurrentQueue {
- FSTAssert([self onTargetQueue],
- @"We are running on the wrong dispatch queue. Expected '%@' Actual: '%@'",
- [self targetQueueLabel], [self currentQueueLabel]);
- FSTAssert(_operationInProgress,
- @"verifyIsCurrentQueue called outside enterCheckedOperation on queue '%@'",
- [self currentQueueLabel]);
+ _impl->VerifyIsCurrentQueue();
}
- (void)enterCheckedOperation:(void (^)(void))block {
- FSTAssert(!_operationInProgress,
- @"enterCheckedOperation may not be called when an operation is in progress");
- @try {
- _operationInProgress = true;
- [self verifyIsCurrentQueue];
- block();
- } @finally {
- _operationInProgress = false;
- }
+ _impl->ExecuteBlocking([block] { block(); });
}
- (void)dispatchAsync:(void (^)(void))block {
- FSTAssert(![self onTargetQueue] || !_operationInProgress,
- @"dispatchAsync called when we are already running on target dispatch queue '%@'",
- [self targetQueueLabel]);
-
- dispatch_async(self.queue, ^{
- [self enterCheckedOperation:block];
- });
+ _impl->Enqueue([block] { block(); });
}
- (void)dispatchAsyncAllowingSameQueue:(void (^)(void))block {
- dispatch_async(self.queue, ^{
- [self enterCheckedOperation:block];
- });
+ _impl->EnqueueRelaxed([block] { block(); });
}
- (void)dispatchSync:(void (^)(void))block {
- FSTAssert(![self onTargetQueue] || !_operationInProgress,
- @"dispatchSync called when we are already running on target dispatch queue '%@'",
- [self targetQueueLabel]);
-
- dispatch_sync(self.queue, ^{
- [self enterCheckedOperation:block];
- });
+ _impl->EnqueueBlocking([block] { block(); });
}
- (FSTDelayedCallback *)dispatchAfterDelay:(NSTimeInterval)delay
timerID:(FSTTimerID)timerID
block:(void (^)(void))block {
- // While not necessarily harmful, we currently don't expect to have multiple callbacks with the
- // same timerID in the queue, so defensively reject them.
- FSTAssert(![self containsDelayedCallbackWithTimerID:timerID],
- @"Attempted to schedule multiple callbacks with id %ld", (unsigned long)timerID);
- FSTDelayedCallback *delayedCallback = [FSTDelayedCallback createAndScheduleWithQueue:self
- timerID:timerID
- delay:delay
- callback:block];
- [self.delayedCallbacks addObject:delayedCallback];
- return delayedCallback;
+ const AsyncQueue::Milliseconds delayMs =
+ std::chrono::milliseconds(static_cast<long long>(delay * 1000));
+ const TimerId convertedTimerId = [FSTDispatchQueue convertTimerId:timerID];
+ DelayedOperation delayed_operation =
+ _impl->EnqueueAfterDelay(delayMs, convertedTimerId, [block] { block(); });
+ return [[FSTDelayedCallback alloc] initWithImpl:std::move(delayed_operation)];
}
- (BOOL)containsDelayedCallbackWithTimerID:(FSTTimerID)timerID {
- NSUInteger matchIndex = [self.delayedCallbacks
- indexOfObjectPassingTest:^BOOL(FSTDelayedCallback *obj, NSUInteger idx, BOOL *stop) {
- return obj.timerID == timerID;
- }];
- return matchIndex != NSNotFound;
+ return _impl->IsScheduled([FSTDispatchQueue convertTimerId:timerID]);
}
- (void)runDelayedCallbacksUntil:(FSTTimerID)lastTimerID {
- dispatch_semaphore_t doneSemaphore = dispatch_semaphore_create(0);
-
- [self dispatchAsync:^{
- FSTAssert(lastTimerID == FSTTimerIDAll || [self containsDelayedCallbackWithTimerID:lastTimerID],
- @"Attempted to run callbacks until missing timer ID: %ld",
- (unsigned long)lastTimerID);
-
- [self sortDelayedCallbacks];
- for (FSTDelayedCallback *callback in self.delayedCallbacks) {
- [callback skipDelay];
- if (lastTimerID != FSTTimerIDAll && callback.timerID == lastTimerID) {
- break;
- }
- }
-
- // Now that the callbacks are queued, we want to enqueue an additional item to release the
- // 'done' semaphore.
- [self dispatchAsyncAllowingSameQueue:^{
- dispatch_semaphore_signal(doneSemaphore);
- }];
- }];
-
- dispatch_semaphore_wait(doneSemaphore, DISPATCH_TIME_FOREVER);
-}
-
-// NOTE: For performance we could store the callbacks sorted (e.g. using std::priority_queue),
-// but this sort only happens in tests (if runDelayedCallbacksUntil: is called), and the size
-// is guaranteed to be small since we don't allow duplicate TimerIds (of which there are only 4).
-- (void)sortDelayedCallbacks {
- // We want to run callbacks in the same order they'd run if they ran naturally.
- [self.delayedCallbacks
- sortUsingComparator:^NSComparisonResult(FSTDelayedCallback *a, FSTDelayedCallback *b) {
- return a.targetTime < b.targetTime
- ? NSOrderedAscending
- : a.targetTime > b.targetTime ? NSOrderedDescending : NSOrderedSame;
- }];
-}
-
-/** Called by FSTDelayedCallback when a callback is run or canceled. */
-- (void)removeDelayedCallback:(FSTDelayedCallback *)callback {
- NSUInteger index = [self.delayedCallbacks indexOfObject:callback];
- FSTAssert(index != NSNotFound, @"Delayed callback not found.");
- [self.delayedCallbacks removeObjectAtIndex:index];
-}
-
-#pragma mark - Private Methods
-
-- (NSString *)currentQueueLabel {
- return [NSString stringWithUTF8String:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)];
-}
-
-- (NSString *)targetQueueLabel {
- return [NSString stringWithUTF8String:dispatch_queue_get_label(self.queue)];
-}
-
-- (BOOL)onTargetQueue {
- return [[self currentQueueLabel] isEqualToString:[self targetQueueLabel]];
+ _impl->RunScheduledOperationsUntil([FSTDispatchQueue convertTimerId:lastTimerID]);
}
@end
diff --git a/Firestore/core/CMakeLists.txt b/Firestore/core/CMakeLists.txt
index 1a0c936..c6ae0f5 100644
--- a/Firestore/core/CMakeLists.txt
+++ b/Firestore/core/CMakeLists.txt
@@ -20,6 +20,7 @@ add_subdirectory(src/firebase/firestore/core)
add_subdirectory(src/firebase/firestore/immutable)
add_subdirectory(src/firebase/firestore/local)
add_subdirectory(src/firebase/firestore/model)
+add_subdirectory(src/firebase/firestore/nanopb)
add_subdirectory(src/firebase/firestore/remote)
add_subdirectory(src/firebase/firestore/util)
diff --git a/Firestore/core/src/firebase/firestore/CMakeLists.txt b/Firestore/core/src/firebase/firestore/CMakeLists.txt
index aad2ebb..9836b05 100644
--- a/Firestore/core/src/firebase/firestore/CMakeLists.txt
+++ b/Firestore/core/src/firebase/firestore/CMakeLists.txt
@@ -18,6 +18,7 @@ cc_library(
SOURCES
geo_point.cc
timestamp.cc
+ timestamp_internal.h
DEPENDS
firebase_firestore_util
)
diff --git a/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt b/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt
index 90ce204..af97b62 100644
--- a/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt
+++ b/Firestore/core/src/firebase/firestore/immutable/CMakeLists.txt
@@ -24,6 +24,7 @@ cc_library(
sorted_map_base.h
sorted_map_base.cc
sorted_map_iterator.h
+ sorted_set.h
tree_sorted_map.h
DEPENDS
firebase_firestore_util
diff --git a/Firestore/core/src/firebase/firestore/immutable/sorted_set.h b/Firestore/core/src/firebase/firestore/immutable/sorted_set.h
new file mode 100644
index 0000000..d78fd61
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/immutable/sorted_set.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_SET_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_SET_H_
+
+#include <algorithm>
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/immutable/sorted_map.h"
+#include "Firestore/core/src/firebase/firestore/immutable/sorted_map_base.h"
+#include "Firestore/core/src/firebase/firestore/util/comparison.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "Firestore/core/src/firebase/firestore/util/hashing.h"
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+
+namespace impl {
+
+// An empty value to associate with keys in the underlying map.
+struct Empty {
+ friend bool operator==(Empty /* left */, Empty /* right */) {
+ return true;
+ }
+};
+
+} // namespace impl
+
+template <typename K,
+ typename V = impl::Empty,
+ typename C = util::Comparator<K>,
+ typename M = SortedMap<K, V, C>>
+class SortedSet {
+ public:
+ using size_type = typename M::size_type;
+ using value_type = K;
+
+ using const_iterator = typename M::const_key_iterator;
+
+ explicit SortedSet(const C& comparator = C()) : map_{comparator} {
+ }
+
+ explicit SortedSet(const M& map) : map_{map} {
+ }
+
+ explicit SortedSet(M&& map) : map_{std::move(map)} {
+ }
+
+ SortedSet(std::initializer_list<value_type> entries, const C& comparator = {})
+ : map_{comparator} {
+ for (auto&& value : entries) {
+ map_ = map_.insert(value, {});
+ }
+ }
+
+ bool empty() const {
+ return map_.empty();
+ }
+
+ size_type size() const {
+ return map_.size();
+ }
+
+ SortedSet insert(const K& key) const {
+ return SortedSet{map_.insert(key, {})};
+ }
+
+ SortedSet erase(const K& key) const {
+ return SortedSet{map_.erase(key)};
+ }
+
+ bool contains(const K& key) const {
+ return map_.contains(key);
+ }
+
+ const_iterator find(const K& key) const {
+ return const_iterator{map_.find(key)};
+ }
+
+ size_type find_index(const K& key) const {
+ return map_.find_index(key);
+ }
+
+ const_iterator min() const {
+ return const_iterator{map_.min()};
+ }
+
+ const K& max() const {
+ return const_iterator{map_.max()};
+ }
+
+ const_iterator begin() const {
+ return const_iterator{map_.begin()};
+ }
+
+ const_iterator end() const {
+ return const_iterator{map_.end()};
+ }
+
+ /**
+ * Returns a view of this SortedSet containing just the keys that have been
+ * inserted that are greater than or equal to the given key.
+ */
+ const util::range<const_iterator> values_from(const K& key) const {
+ return map_.keys_from(key);
+ }
+
+ /**
+ * Returns a view of this SortedSet containing just the keys that have been
+ * inserted that are greater than or equal to the given start_key and less
+ * than the given end_key.
+ */
+ const util::range<const_iterator> values_in(const K& start_key,
+ const K& end_key) const {
+ return map_.keys_in(start_key, end_key);
+ }
+
+ friend bool operator==(const SortedSet& lhs, const SortedSet& rhs) {
+ if (lhs.size() != rhs.size()) {
+ return false;
+ }
+ return std::equal(lhs.begin(), lhs.end(), rhs.begin());
+ }
+
+ friend bool operator!=(const SortedSet& lhs, const SortedSet& rhs) {
+ return !(lhs == rhs);
+ }
+
+ private:
+ M map_;
+};
+
+template <typename K, typename V, typename C>
+SortedSet<K, V, C> MakeSortedSet(const SortedMap<K, V, C>& map) {
+ return SortedSet<K, V, C>{map};
+}
+
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_SET_H_
diff --git a/Firestore/core/src/firebase/firestore/model/base_path.h b/Firestore/core/src/firebase/firestore/model/base_path.h
index bc1f89d..58df6f0 100644
--- a/Firestore/core/src/firebase/firestore/model/base_path.h
+++ b/Firestore/core/src/firebase/firestore/model/base_path.h
@@ -25,6 +25,7 @@
#include <vector>
#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "Firestore/core/src/firebase/firestore/util/hashing.h"
namespace firebase {
namespace firestore {
@@ -159,19 +160,6 @@ class BasePath {
return segments_ >= rhs.segments_;
}
-#if defined(__OBJC__)
- // For Objective-C++ hash; to be removed after migration.
- // Do NOT use in C++ code.
- NSUInteger Hash() const {
- std::hash<std::string> hash_fn;
- NSUInteger hash_result = 0;
- for (const std::string& segment : segments_) {
- hash_result = hash_result * 31u + hash_fn(segment);
- }
- return hash_result;
- }
-#endif // defined(__OBJC__)
-
protected:
BasePath() = default;
template <typename IterT>
diff --git a/Firestore/core/src/firebase/firestore/model/database_id.h b/Firestore/core/src/firebase/firestore/model/database_id.h
index 0c0e0ec..c432b8f 100644
--- a/Firestore/core/src/firebase/firestore/model/database_id.h
+++ b/Firestore/core/src/firebase/firestore/model/database_id.h
@@ -20,6 +20,7 @@
#include <cstdint>
#include <string>
+#include "Firestore/core/src/firebase/firestore/util/hashing.h"
#include "absl/strings/string_view.h"
namespace firebase {
@@ -62,9 +63,8 @@ class DatabaseId {
#if defined(__OBJC__)
// For objective-c++ hash; to be removed after migration.
// Do NOT use in C++ code.
- NSUInteger Hash() const {
- std::hash<std::string> hash_fn;
- return hash_fn(project_id_) * 31u + hash_fn(database_id_);
+ size_t Hash() const {
+ return util::Hash(project_id_, database_id_);
}
#endif // defined(__OBJC__)
diff --git a/Firestore/core/src/firebase/firestore/model/document_key.h b/Firestore/core/src/firebase/firestore/model/document_key.h
index 4bdc04b..3f5f342 100644
--- a/Firestore/core/src/firebase/firestore/model/document_key.h
+++ b/Firestore/core/src/firebase/firestore/model/document_key.h
@@ -17,6 +17,7 @@
#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_H_
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_H_
+#include <functional>
#include <initializer_list>
#include <memory>
#include <string>
@@ -26,6 +27,8 @@
#endif // defined(__OBJC__)
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/util/comparison.h"
+#include "Firestore/core/src/firebase/firestore/util/hashing.h"
#include "absl/strings/string_view.h"
namespace firebase {
@@ -56,15 +59,15 @@ class DocumentKey {
return [FSTDocumentKey keyWithPath:path()];
}
- std::string ToString() const {
- return path().CanonicalString();
- }
-
NSUInteger Hash() const {
- return std::hash<std::string>{}(ToString());
+ return util::Hash(ToString());
}
#endif
+ std::string ToString() const {
+ return path().CanonicalString();
+ }
+
/**
* Creates and returns a new document key using '/' to split the string into
* segments.
@@ -116,7 +119,20 @@ inline bool operator>=(const DocumentKey& lhs, const DocumentKey& rhs) {
return lhs.path() >= rhs.path();
}
+struct DocumentKeyHash {
+ size_t operator()(const DocumentKey& key) const {
+ return util::Hash(key.path());
+ }
+};
+
} // namespace model
+
+namespace util {
+
+template <>
+struct Comparator<model::DocumentKey> : public std::less<model::DocumentKey> {};
+
+} // namespace util
} // namespace firestore
} // namespace firebase
diff --git a/Firestore/Source/Model/FSTDocumentKeySet.h b/Firestore/core/src/firebase/firestore/model/document_key_set.h
index 80f6624..1301bf5 100644
--- a/Firestore/Source/Model/FSTDocumentKeySet.h
+++ b/Firestore/core/src/firebase/firestore/model/document_key_set.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 Google
+ * Copyright 2018 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,22 +14,21 @@
* limitations under the License.
*/
-#import <Foundation/Foundation.h>
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_SET_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_SET_H_
-#import "Firestore/third_party/Immutable/FSTImmutableSortedSet.h"
+#include "Firestore/core/src/firebase/firestore/immutable/sorted_set.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
-@class FSTDocumentKey;
-
-NS_ASSUME_NONNULL_BEGIN
+namespace firebase {
+namespace firestore {
+namespace model {
/** Convenience type for a set of keys, since they are so common. */
-typedef FSTImmutableSortedSet<FSTDocumentKey *> FSTDocumentKeySet;
-
-@interface FSTImmutableSortedSet (FSTDocumentKey)
-
-/** Returns a new set using the DocumentKeyComparator. */
-+ (FSTDocumentKeySet *)keySet;
+using DocumentKeySet = immutable::SortedSet<DocumentKey>;
-@end
+} // namespace model
+} // namespace firestore
+} // namespace firebase
-NS_ASSUME_NONNULL_END
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_DOCUMENT_KEY_SET_H_
diff --git a/Firestore/core/src/firebase/firestore/model/field_mask.h b/Firestore/core/src/firebase/firestore/model/field_mask.h
index b895ab3..431e05a 100644
--- a/Firestore/core/src/firebase/firestore/model/field_mask.h
+++ b/Firestore/core/src/firebase/firestore/model/field_mask.h
@@ -23,6 +23,7 @@
#include <vector>
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
+#include "Firestore/core/src/firebase/firestore/util/hashing.h"
namespace firebase {
namespace firestore {
@@ -55,6 +56,22 @@ class FieldMask {
return fields_.end();
}
+ /**
+ * Verifies that `fieldPath` is included by at least one field in this field
+ * mask.
+ *
+ * This is an O(n) operation, where `n` is the size of the field mask.
+ */
+ bool covers(const FieldPath& fieldPath) const {
+ for (const FieldPath& fieldMaskPath : fields_) {
+ if (fieldMaskPath.IsPrefixOf(fieldPath)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
std::string ToString() const {
// Ideally, one should use a string builder. Since this is only non-critical
// code for logging and debugging, the logic is kept simple here.
@@ -70,11 +87,7 @@ class FieldMask {
}
NSUInteger Hash() const {
- NSUInteger hashResult = 0;
- for (const FieldPath& field : fields_) {
- hashResult = hashResult * 31u + field.Hash();
- }
- return hashResult;
+ return util::Hash(fields_);
}
#endif
diff --git a/Firestore/core/src/firebase/firestore/model/field_transform.h b/Firestore/core/src/firebase/firestore/model/field_transform.h
index a1dd96c..1a7127a 100644
--- a/Firestore/core/src/firebase/firestore/model/field_transform.h
+++ b/Firestore/core/src/firebase/firestore/model/field_transform.h
@@ -22,6 +22,7 @@
#include "Firestore/core/src/firebase/firestore/model/field_path.h"
#include "Firestore/core/src/firebase/firestore/model/transform_operations.h"
+#include "Firestore/core/src/firebase/firestore/util/hashing.h"
namespace firebase {
namespace firestore {
@@ -51,9 +52,7 @@ class FieldTransform {
// For Objective-C++ hash; to be removed after migration.
// Do NOT use in C++ code.
NSUInteger Hash() const {
- NSUInteger hash = path_.Hash();
- hash = hash * 31 + transformation_->Hash();
- return hash;
+ return util::Hash(path_, transformation_->Hash());
}
#endif // defined(__OBJC__)
diff --git a/Firestore/core/src/firebase/firestore/model/precondition.h b/Firestore/core/src/firebase/firestore/model/precondition.h
index 4ab03c2..b98bb45 100644
--- a/Firestore/core/src/firebase/firestore/model/precondition.h
+++ b/Firestore/core/src/firebase/firestore/model/precondition.h
@@ -21,7 +21,6 @@
#if defined(__OBJC__)
#import "FIRTimestamp.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Model/FSTDocument.h"
#include "Firestore/core/include/firebase/firestore/timestamp.h"
#endif // defined(__OBJC__)
diff --git a/Firestore/core/src/firebase/firestore/model/snapshot_version.h b/Firestore/core/src/firebase/firestore/model/snapshot_version.h
index 1fbba1c..dbecea1 100644
--- a/Firestore/core/src/firebase/firestore/model/snapshot_version.h
+++ b/Firestore/core/src/firebase/firestore/model/snapshot_version.h
@@ -19,11 +19,6 @@
#include "Firestore/core/include/firebase/firestore/timestamp.h"
-#if defined(__OBJC__)
-#import "FIRTimestamp.h"
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
-#endif // defined(__OBJC__)
-
namespace firebase {
namespace firestore {
namespace model {
@@ -34,6 +29,11 @@ namespace model {
*/
class SnapshotVersion {
public:
+#if __OBJC__
+ SnapshotVersion() {
+ }
+#endif // __OBJC__
+
explicit SnapshotVersion(const Timestamp& timestamp);
const Timestamp& timestamp() const {
@@ -43,23 +43,11 @@ class SnapshotVersion {
/** Creates a new version that is smaller than all other versions. */
static const SnapshotVersion& None();
-#if defined(__OBJC__)
- SnapshotVersion(FSTSnapshotVersion* version) // NOLINT(runtime/explicit)
- : timestamp_{version.timestamp.seconds, version.timestamp.nanoseconds} {
- }
-
- operator FSTSnapshotVersion*() const {
- if (timestamp_ == Timestamp{}) {
- return [FSTSnapshotVersion noVersion];
- } else {
- return [FSTSnapshotVersion
- versionWithTimestamp:[FIRTimestamp
- timestampWithSeconds:timestamp_.seconds()
- nanoseconds:timestamp_
- .nanoseconds()]];
- }
+#if __OBJC__
+ size_t Hash() const {
+ return std::hash<Timestamp>{}(timestamp_);
}
-#endif // defined(__OBJC__)
+#endif // __OBJC__
private:
Timestamp timestamp_;
diff --git a/Firestore/core/src/firebase/firestore/model/transform_operations.h b/Firestore/core/src/firebase/firestore/model/transform_operations.h
index a1c2de0..8eed7ae 100644
--- a/Firestore/core/src/firebase/firestore/model/transform_operations.h
+++ b/Firestore/core/src/firebase/firestore/model/transform_operations.h
@@ -17,12 +17,16 @@
#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TRANSFORM_OPERATIONS_H_
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_MODEL_TRANSFORM_OPERATIONS_H_
+#if !defined(__OBJC__)
+#error "This header only supports Objective-C++."
+#endif // !defined(__OBJC__)
+
#include <utility>
#include <vector>
-#if defined(__OBJC__)
#import "Firestore/Source/Model/FSTFieldValue.h"
-#endif
+
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
namespace firebase {
namespace firestore {
@@ -49,6 +53,20 @@ class TransformOperation {
/** Returns the actual type. */
virtual Type type() const = 0;
+ /**
+ * Computes the local transform result against the provided `previousValue`,
+ * optionally using the provided localWriteTime.
+ */
+ virtual FSTFieldValue* ApplyToLocalView(
+ FSTFieldValue* previousValue, FIRTimestamp* localWriteTime) const = 0;
+
+ /**
+ * Computes a final transform result after the transform has been acknowledged
+ * by the server, potentially using the server-provided transformResult.
+ */
+ virtual FSTFieldValue* ApplyToRemoteDocument(
+ FSTFieldValue* previousValue, FSTFieldValue* transformResult) const = 0;
+
/** Returns whether the two are equal. */
virtual bool operator==(const TransformOperation& other) const = 0;
@@ -57,11 +75,9 @@ class TransformOperation {
return !operator==(other);
}
-#if defined(__OBJC__)
// For Objective-C++ hash; to be removed after migration.
// Do NOT use in C++ code.
virtual NSUInteger Hash() const = 0;
-#endif // defined(__OBJC__)
};
/** Transforms a value into a server-generated timestamp. */
@@ -74,6 +90,19 @@ class ServerTimestampTransform : public TransformOperation {
return Type::ServerTimestamp;
}
+ FSTFieldValue* ApplyToLocalView(FSTFieldValue* previousValue,
+ FIRTimestamp* localWriteTime) const override {
+ return [FSTServerTimestampValue
+ serverTimestampValueWithLocalWriteTime:localWriteTime
+ previousValue:previousValue];
+ }
+
+ FSTFieldValue* ApplyToRemoteDocument(
+ FSTFieldValue* /* previousValue */,
+ FSTFieldValue* transformResult) const override {
+ return transformResult;
+ }
+
bool operator==(const TransformOperation& other) const override {
// All ServerTimestampTransform objects are equal.
return other.type() == Type::ServerTimestamp;
@@ -84,7 +113,6 @@ class ServerTimestampTransform : public TransformOperation {
return shared_instance;
}
-#if defined(__OBJC__)
// For Objective-C++ hash; to be removed after migration.
// Do NOT use in C++ code.
NSUInteger Hash() const override {
@@ -92,16 +120,12 @@ class ServerTimestampTransform : public TransformOperation {
// instances are equal.
return 37;
}
-#endif // defined(__OBJC__)
private:
ServerTimestampTransform() {
}
};
-// TODO(mikelehen): ArrayTransform can only be used from Obj-C until we switch
-// to using FieldValue instead of FSTFieldValue.
-#if defined(__OBJC__)
/**
* Transforms an array via a union or remove operation (for convenience, we use
* this class for both Type::ArrayUnion and Type::ArrayRemove).
@@ -119,6 +143,21 @@ class ArrayTransform : public TransformOperation {
return type_;
}
+ FSTFieldValue* ApplyToLocalView(
+ FSTFieldValue* previousValue,
+ FIRTimestamp* /* localWriteTime */) const override {
+ return Apply(previousValue);
+ }
+
+ FSTFieldValue* ApplyToRemoteDocument(
+ FSTFieldValue* previousValue,
+ FSTFieldValue* /* transformResult */) const override {
+ // The server just sends null as the transform result for array operations,
+ // so we have to calculate a result the same as we do for local
+ // applications.
+ return Apply(previousValue);
+ }
+
const std::vector<FSTFieldValue*>& elements() const {
return elements_;
}
@@ -131,7 +170,7 @@ class ArrayTransform : public TransformOperation {
if (array_transform.elements_.size() != elements_.size()) {
return false;
}
- for (int i = 0; i < elements_.size(); i++) {
+ for (size_t i = 0; i < elements_.size(); i++) {
if (![array_transform.elements_[i] isEqual:elements_[i]]) {
return false;
}
@@ -139,7 +178,6 @@ class ArrayTransform : public TransformOperation {
return true;
}
-#if defined(__OBJC__)
// For Objective-C++ hash; to be removed after migration.
// Do NOT use in C++ code.
NSUInteger Hash() const override {
@@ -150,7 +188,6 @@ class ArrayTransform : public TransformOperation {
}
return result;
}
-#endif // defined(__OBJC__)
static const std::vector<FSTFieldValue*>& Elements(
const TransformOperation& op) {
@@ -162,8 +199,39 @@ class ArrayTransform : public TransformOperation {
private:
Type type_;
std::vector<FSTFieldValue*> elements_;
+
+ /**
+ * Inspects the provided value, returning a mutable copy of the internal array
+ * if it's an FSTArrayValue and an empty mutable array if it's nil or any
+ * other type of FSTFieldValue.
+ */
+ static NSMutableArray<FSTFieldValue*>* CoercedFieldValuesArray(
+ FSTFieldValue* value) {
+ if ([value isMemberOfClass:[FSTArrayValue class]]) {
+ return [NSMutableArray
+ arrayWithArray:reinterpret_cast<FSTArrayValue*>(value).internalValue];
+ } else {
+ // coerce to empty array.
+ return [NSMutableArray array];
+ }
+ }
+
+ FSTFieldValue* Apply(FSTFieldValue* previousValue) const {
+ NSMutableArray<FSTFieldValue*>* result =
+ ArrayTransform::CoercedFieldValuesArray(previousValue);
+ for (FSTFieldValue* element : elements_) {
+ if (type_ == Type::ArrayUnion) {
+ if (![result containsObject:element]) {
+ [result addObject:element];
+ }
+ } else {
+ FIREBASE_ASSERT(type_ == Type::ArrayRemove);
+ [result removeObject:element];
+ }
+ }
+ return [[FSTArrayValue alloc] initWithValueNoCopy:result];
+ }
};
-#endif
} // namespace model
} // namespace firestore
diff --git a/Firestore/core/src/firebase/firestore/nanopb/CMakeLists.txt b/Firestore/core/src/firebase/firestore/nanopb/CMakeLists.txt
new file mode 100644
index 0000000..82ffb65
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/nanopb/CMakeLists.txt
@@ -0,0 +1,27 @@
+# Copyright 2018 Google
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+cc_library(
+ firebase_firestore_nanopb
+ SOURCES
+ tag.h
+ reader.h
+ reader.cc
+ writer.h
+ writer.cc
+ DEPENDS
+ firebase_firestore_util
+ firebase_firestore_protos_nanopb
+ nanopb
+)
diff --git a/Firestore/core/src/firebase/firestore/nanopb/reader.cc b/Firestore/core/src/firebase/firestore/nanopb/reader.cc
new file mode 100644
index 0000000..86e38ac
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/nanopb/reader.cc
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/src/firebase/firestore/nanopb/reader.h"
+
+#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h"
+
+namespace firebase {
+namespace firestore {
+namespace nanopb {
+
+using firebase::firestore::util::Status;
+using std::int64_t;
+using std::uint64_t;
+
+Reader Reader::Wrap(const uint8_t* bytes, size_t length) {
+ return Reader{pb_istream_from_buffer(bytes, length)};
+}
+
+Tag Reader::ReadTag() {
+ Tag tag;
+ if (!status_.ok()) return tag;
+
+ bool eof;
+ if (!pb_decode_tag(&stream_, &tag.wire_type, &tag.field_number, &eof)) {
+ status_ = Status(FirestoreErrorCode::DataLoss, PB_GET_ERROR(&stream_));
+ return tag;
+ }
+
+ // nanopb code always returns a false status when setting eof.
+ FIREBASE_ASSERT_MESSAGE(!eof, "nanopb set both ok status and eof to true");
+
+ return tag;
+}
+
+void Reader::ReadNanopbMessage(const pb_field_t fields[], void* dest_struct) {
+ if (!status_.ok()) return;
+
+ if (!pb_decode(&stream_, fields, dest_struct)) {
+ status_ = Status(FirestoreErrorCode::DataLoss, PB_GET_ERROR(&stream_));
+ }
+}
+
+/**
+ * Note that (despite the return type) this works for bool, enum, int32, int64,
+ * uint32 and uint64 proto field types.
+ *
+ * Note: This is not expected to be called directly, but rather only via the
+ * other Decode* methods (i.e. DecodeBool, DecodeLong, etc)
+ *
+ * @return The decoded varint as a uint64_t.
+ */
+uint64_t Reader::ReadVarint() {
+ if (!status_.ok()) return 0;
+
+ uint64_t varint_value = 0;
+ if (!pb_decode_varint(&stream_, &varint_value)) {
+ status_ = Status(FirestoreErrorCode::DataLoss, PB_GET_ERROR(&stream_));
+ }
+ return varint_value;
+}
+
+void Reader::ReadNull() {
+ uint64_t varint = ReadVarint();
+ if (!status_.ok()) return;
+
+ if (varint != google_protobuf_NullValue_NULL_VALUE) {
+ status_ = Status(FirestoreErrorCode::DataLoss,
+ "Input proto bytes cannot be parsed (invalid null value)");
+ }
+}
+
+bool Reader::ReadBool() {
+ uint64_t varint = ReadVarint();
+ if (!status_.ok()) return false;
+
+ switch (varint) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ status_ =
+ Status(FirestoreErrorCode::DataLoss,
+ "Input proto bytes cannot be parsed (invalid bool value)");
+ return false;
+ }
+}
+
+int64_t Reader::ReadInteger() {
+ return ReadVarint();
+}
+
+std::string Reader::ReadString() {
+ if (!status_.ok()) return "";
+
+ pb_istream_t substream;
+ if (!pb_make_string_substream(&stream_, &substream)) {
+ status_ = Status(FirestoreErrorCode::DataLoss, PB_GET_ERROR(&stream_));
+ pb_close_string_substream(&stream_, &substream);
+ return "";
+ }
+
+ std::string result(substream.bytes_left, '\0');
+ if (!pb_read(&substream, reinterpret_cast<pb_byte_t*>(&result[0]),
+ substream.bytes_left)) {
+ status_ = Status(FirestoreErrorCode::DataLoss, PB_GET_ERROR(&stream_));
+ pb_close_string_substream(&stream_, &substream);
+ return "";
+ }
+
+ // NB: future versions of nanopb read the remaining characters out of the
+ // substream (and return false if that fails) as an additional safety
+ // check within pb_close_string_substream. Unfortunately, that's not present
+ // in the current version (0.38). We'll make a stronger assertion and check
+ // to make sure there *are* no remaining characters in the substream.
+ FIREBASE_ASSERT_MESSAGE(
+ substream.bytes_left == 0,
+ "Bytes remaining in substream after supposedly reading all of them.");
+
+ pb_close_string_substream(&stream_, &substream);
+
+ return result;
+}
+
+} // namespace nanopb
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/nanopb/reader.h b/Firestore/core/src/firebase/firestore/nanopb/reader.h
new file mode 100644
index 0000000..093e20b
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/nanopb/reader.h
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_READER_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_READER_H_
+
+#include <pb.h>
+#include <pb_decode.h>
+
+#include <cstdint>
+#include <functional>
+#include <string>
+
+#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
+#include "Firestore/core/src/firebase/firestore/nanopb/tag.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "Firestore/core/src/firebase/firestore/util/status.h"
+
+namespace firebase {
+namespace firestore {
+namespace nanopb {
+
+/**
+ * Docs TODO(rsgowman). But currently, this just wraps the underlying nanopb
+ * pb_istream_t.
+ */
+class Reader {
+ public:
+ /**
+ * Creates an input stream that reads from the specified bytes. Note that
+ * this reference must remain valid for the lifetime of this Reader.
+ *
+ * (This is roughly equivalent to the nanopb function
+ * pb_istream_from_buffer())
+ *
+ * @param bytes where the input should be deserialized from.
+ */
+ static Reader Wrap(const uint8_t* bytes, size_t length);
+
+ /**
+ * Reads a message type from the input stream.
+ *
+ * This essentially wraps calls to nanopb's pb_decode_tag() method.
+ */
+ Tag ReadTag();
+
+ /**
+ * Reads a nanopb message from the input stream.
+ *
+ * This essentially wraps calls to nanopb's pb_decode() method. If we didn't
+ * use `oneof`s in our protos, this would be the primary way of decoding
+ * messages.
+ */
+ void ReadNanopbMessage(const pb_field_t fields[], void* dest_struct);
+
+ void ReadNull();
+ bool ReadBool();
+ std::int64_t ReadInteger();
+
+ std::string ReadString();
+
+ /**
+ * Reads a message and its length.
+ *
+ * Analog to Writer::WriteNestedMessage(). See that methods docs for further
+ * details.
+ *
+ * Call this method when reading a nested message. Provide a function to read
+ * the message itself.
+ *
+ * @param read_message_fn Function to read the submessage. Note that this
+ * function is expected to check the Reader's status (via
+ * Reader::status().ok()) and if not ok, to return a placeholder/invalid
+ * value.
+ */
+ template <typename T>
+ T ReadNestedMessage(const std::function<T(Reader*)>& read_message_fn);
+
+ size_t bytes_left() const {
+ return stream_.bytes_left;
+ }
+
+ util::Status status() const {
+ return status_;
+ }
+
+ void set_status(util::Status status) {
+ status_ = status;
+ }
+
+ private:
+ /**
+ * Creates a new Reader, based on the given nanopb pb_istream_t. Note that
+ * a shallow copy will be taken. (Non-null pointers within this struct must
+ * remain valid for the lifetime of this Reader.)
+ */
+ explicit Reader(pb_istream_t stream) : stream_(stream) {
+ }
+
+ /**
+ * Reads a "varint" from the input stream.
+ *
+ * This essentially wraps calls to nanopb's pb_decode_varint() method.
+ *
+ * Note that (despite the return type) this works for bool, enum, int32,
+ * int64, uint32 and uint64 proto field types.
+ *
+ * Note: This is not expected to be called direclty, but rather only via the
+ * other Decode* methods (i.e. DecodeBool, DecodeLong, etc)
+ *
+ * @return The decoded varint as a uint64_t.
+ */
+ std::uint64_t ReadVarint();
+
+ util::Status status_ = util::Status::OK();
+
+ pb_istream_t stream_;
+};
+
+template <typename T>
+T Reader::ReadNestedMessage(const std::function<T(Reader*)>& read_message_fn) {
+ // Implementation note: This is roughly modeled on pb_decode_delimited,
+ // adjusted to account for the oneof in FieldValue.
+
+ if (!status_.ok()) return read_message_fn(this);
+
+ pb_istream_t raw_substream;
+ if (!pb_make_string_substream(&stream_, &raw_substream)) {
+ status_ =
+ util::Status(FirestoreErrorCode::DataLoss, PB_GET_ERROR(&stream_));
+ pb_close_string_substream(&stream_, &raw_substream);
+ return read_message_fn(this);
+ }
+ Reader substream(raw_substream);
+
+ // If this fails, we *won't* return right away so that we can cleanup the
+ // substream (although technically, that turns out not to matter; no resource
+ // leaks occur if we don't do this.)
+ // TODO(rsgowman): Consider RAII here. (Watch out for Reader class which also
+ // wraps streams.)
+ T message = read_message_fn(&substream);
+ status_ = substream.status();
+
+ // NB: future versions of nanopb read the remaining characters out of the
+ // substream (and return false if that fails) as an additional safety
+ // check within pb_close_string_substream. Unfortunately, that's not present
+ // in the current version (0.38). We'll make a stronger assertion and check
+ // to make sure there *are* no remaining characters in the substream.
+ FIREBASE_ASSERT_MESSAGE(
+ substream.bytes_left() == 0,
+ "Bytes remaining in substream after supposedly reading all of them.");
+
+ pb_close_string_substream(&stream_, &substream.stream_);
+
+ return message;
+}
+
+} // namespace nanopb
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_READER_H_
diff --git a/Firestore/core/src/firebase/firestore/nanopb/tag.h b/Firestore/core/src/firebase/firestore/nanopb/tag.h
new file mode 100644
index 0000000..455ef0c
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/nanopb/tag.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_TAG_H__
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_TAG_H__
+
+#include <pb.h>
+
+namespace firebase {
+namespace firestore {
+namespace nanopb {
+
+/**
+ * Represents a nanopb tag.
+ *
+ * field_number is one of the field tags that nanopb generates based off of
+ * the proto messages. They're typically named in the format:
+ * <parentNameSpace>_<childNameSpace>_<message>_<field>_tag, e.g.
+ * google_firestore_v1beta1_Document_name_tag.
+ */
+struct Tag {
+ pb_wire_type_t wire_type;
+ uint32_t field_number;
+};
+
+} // namespace nanopb
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_TAG_H_
diff --git a/Firestore/core/src/firebase/firestore/nanopb/writer.cc b/Firestore/core/src/firebase/firestore/nanopb/writer.cc
new file mode 100644
index 0000000..cbee989
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/nanopb/writer.cc
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/src/firebase/firestore/nanopb/writer.h"
+
+#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h"
+
+namespace firebase {
+namespace firestore {
+namespace nanopb {
+
+using firebase::firestore::util::Status;
+using std::int64_t;
+using std::int8_t;
+using std::uint64_t;
+
+Writer Writer::Wrap(std::vector<uint8_t>* out_bytes) {
+ // TODO(rsgowman): find a better home for this constant.
+ // A document is defined to have a max size of 1MiB - 4 bytes.
+ static const size_t kMaxDocumentSize = 1 * 1024 * 1024 - 4;
+
+ // Construct a nanopb output stream.
+ //
+ // Set the max_size to be the max document size (as an upper bound; one would
+ // expect individual FieldValue's to be smaller than this).
+ //
+ // bytes_written is (always) initialized to 0. (NB: nanopb does not know or
+ // care about the underlying output vector, so where we are in the vector
+ // itself is irrelevant. i.e. don't use out_bytes->size())
+ pb_ostream_t raw_stream = {
+ /*callback=*/[](pb_ostream_t* stream, const pb_byte_t* buf,
+ size_t count) -> bool {
+ auto* out_bytes = static_cast<std::vector<uint8_t>*>(stream->state);
+ out_bytes->insert(out_bytes->end(), buf, buf + count);
+ return true;
+ },
+ /*state=*/out_bytes,
+ /*max_size=*/kMaxDocumentSize,
+ /*bytes_written=*/0,
+ /*errmsg=*/nullptr};
+ return Writer(raw_stream);
+}
+
+void Writer::WriteTag(Tag tag) {
+ if (!status_.ok()) return;
+
+ if (!pb_encode_tag(&stream_, tag.wire_type, tag.field_number)) {
+ FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
+ }
+}
+
+void Writer::WriteNanopbMessage(const pb_field_t fields[],
+ const void* src_struct) {
+ if (!status_.ok()) return;
+
+ if (!pb_encode(&stream_, fields, src_struct)) {
+ FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
+ }
+}
+
+void Writer::WriteSize(size_t size) {
+ return WriteVarint(size);
+}
+
+void Writer::WriteVarint(uint64_t value) {
+ if (!status_.ok()) return;
+
+ if (!pb_encode_varint(&stream_, value)) {
+ FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
+ }
+}
+
+void Writer::WriteNull() {
+ return WriteVarint(google_protobuf_NullValue_NULL_VALUE);
+}
+
+void Writer::WriteBool(bool bool_value) {
+ return WriteVarint(bool_value);
+}
+
+void Writer::WriteInteger(int64_t integer_value) {
+ return WriteVarint(integer_value);
+}
+
+void Writer::WriteString(const std::string& string_value) {
+ if (!status_.ok()) return;
+
+ if (!pb_encode_string(
+ &stream_, reinterpret_cast<const pb_byte_t*>(string_value.c_str()),
+ string_value.length())) {
+ FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
+ }
+}
+
+void Writer::WriteNestedMessage(
+ const std::function<void(Writer*)>& write_message_fn) {
+ if (!status_.ok()) return;
+
+ // First calculate the message size using a non-writing substream.
+ Writer sizer = Writer::Sizing();
+ write_message_fn(&sizer);
+ status_ = sizer.status();
+ if (!status_.ok()) return;
+ size_t size = sizer.bytes_written();
+
+ // Write out the size to the output writer.
+ WriteSize(size);
+ if (!status_.ok()) return;
+
+ // If this stream is itself a sizing stream, then we don't need to actually
+ // parse field_value a second time; just update the bytes_written via a call
+ // to pb_write. (If we try to write the contents into a sizing stream, it'll
+ // fail since sizing streams don't actually have any buffer space.)
+ if (stream_.callback == nullptr) {
+ if (!pb_write(&stream_, nullptr, size)) {
+ FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
+ }
+ return;
+ }
+
+ // Ensure the output stream has enough space
+ if (stream_.bytes_written + size > stream_.max_size) {
+ FIREBASE_ASSERT_MESSAGE(
+ false,
+ "Insufficient space in the output stream to write the given message");
+ }
+
+ // Use a substream to verify that a callback doesn't write more than what it
+ // did the first time. (Use an initializer rather than setting fields
+ // individually like nanopb does. This gives us a *chance* of noticing if
+ // nanopb adds new fields.)
+ Writer writer({stream_.callback, stream_.state,
+ /*max_size=*/size, /*bytes_written=*/0,
+ /*errmsg=*/nullptr});
+ write_message_fn(&writer);
+ status_ = writer.status();
+ if (!status_.ok()) return;
+
+ stream_.bytes_written += writer.stream_.bytes_written;
+ stream_.state = writer.stream_.state;
+ stream_.errmsg = writer.stream_.errmsg;
+
+ if (writer.bytes_written() != size) {
+ // submsg size changed
+ FIREBASE_ASSERT_MESSAGE(
+ false, "Parsing the nested message twice yielded different sizes");
+ }
+}
+
+} // namespace nanopb
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/nanopb/writer.h b/Firestore/core/src/firebase/firestore/nanopb/writer.h
new file mode 100644
index 0000000..e428826
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/nanopb/writer.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_WRITER_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_WRITER_H_
+
+#include <pb.h>
+#include <pb_encode.h>
+
+#include <cstdint>
+#include <functional>
+#include <string>
+#include <vector>
+
+#include "Firestore/core/src/firebase/firestore/nanopb/tag.h"
+#include "Firestore/core/src/firebase/firestore/util/status.h"
+
+namespace firebase {
+namespace firestore {
+namespace nanopb {
+
+/**
+ * Docs TODO(rsgowman). But currently, this just wraps the underlying nanopb
+ * pb_ostream_t. Also doc how to check status.
+ */
+class Writer {
+ public:
+ /**
+ * Creates an output stream that writes to the specified vector. Note that
+ * this vector pointer must remain valid for the lifetime of this Writer.
+ *
+ * (This is roughly equivalent to the nanopb function
+ * pb_ostream_from_buffer())
+ *
+ * @param out_bytes where the output should be serialized to.
+ */
+ static Writer Wrap(std::vector<std::uint8_t>* out_bytes);
+
+ /**
+ * Creates a non-writing output stream used to calculate the size of
+ * the serialized output.
+ */
+ static Writer Sizing() {
+ return Writer(PB_OSTREAM_SIZING);
+ }
+
+ /**
+ * Writes a message type to the output stream.
+ *
+ * This essentially wraps calls to nanopb's pb_encode_tag() method.
+ */
+ void WriteTag(Tag tag);
+
+ /**
+ * Writes a nanopb message to the output stream.
+ *
+ * This essentially wraps calls to nanopb's `pb_encode()` method. If we didn't
+ * use `oneof`s in our protos, this would be the primary way of encoding
+ * messages.
+ */
+ void WriteNanopbMessage(const pb_field_t fields[], const void* src_struct);
+
+ void WriteSize(size_t size);
+ void WriteNull();
+ void WriteBool(bool bool_value);
+ void WriteInteger(std::int64_t integer_value);
+
+ void WriteString(const std::string& string_value);
+
+ /**
+ * Writes a message and its length.
+ *
+ * When writing a top level message, protobuf doesn't include the length
+ * (since you can get that already from the length of the binary output.) But
+ * when writing a sub/nested message, you must include the length in the
+ * serialization.
+ *
+ * Call this method when writing a nested message. Provide a function to
+ * write the message itself. This method will calculate the size of the
+ * written message (using the provided function with a non-writing sizing
+ * stream), write out the size (and perform sanity checks), and then serialize
+ * the message by calling the provided function a second time.
+ */
+ void WriteNestedMessage(const std::function<void(Writer*)>& write_message_fn);
+
+ size_t bytes_written() const {
+ return stream_.bytes_written;
+ }
+
+ util::Status status() const {
+ return status_;
+ }
+
+ private:
+ util::Status status_ = util::Status::OK();
+
+ /**
+ * Creates a new Writer, based on the given nanopb pb_ostream_t. Note that
+ * a shallow copy will be taken. (Non-null pointers within this struct must
+ * remain valid for the lifetime of this Writer.)
+ */
+ explicit Writer(const pb_ostream_t& stream) : stream_(stream) {
+ }
+
+ /**
+ * Writes a "varint" to the output stream.
+ *
+ * This essentially wraps calls to nanopb's pb_encode_varint() method.
+ *
+ * Note that (despite the value parameter type) this works for bool, enum,
+ * int32, int64, uint32 and uint64 proto field types.
+ *
+ * Note: This is not expected to be called directly, but rather only
+ * via the other Write* methods (i.e. WriteBool, WriteLong, etc)
+ *
+ * @param value The value to write, represented as a uint64_t.
+ */
+ void WriteVarint(std::uint64_t value);
+
+ pb_ostream_t stream_;
+};
+
+} // namespace nanopb
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_NANOPB_WRITER_H_
diff --git a/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt b/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt
index 7f528fb..fc51b37 100644
--- a/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt
+++ b/Firestore/core/src/firebase/firestore/remote/CMakeLists.txt
@@ -21,7 +21,9 @@ cc_library(
serializer.cc
DEPENDS
firebase_firestore_model
+ firebase_firestore_nanopb
firebase_firestore_protos_nanopb
+ firebase_firestore_util
grpc::grpc
nanopb
)
diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.cc b/Firestore/core/src/firebase/firestore/remote/serializer.cc
index b5a0720..6240c21 100644
--- a/Firestore/core/src/firebase/firestore/remote/serializer.cc
+++ b/Firestore/core/src/firebase/firestore/remote/serializer.cc
@@ -25,364 +25,84 @@
#include <utility>
#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h"
+#include "Firestore/Protos/nanopb/google/firestore/v1beta1/firestore.pb.h"
+#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
+#include "Firestore/core/src/firebase/firestore/model/document.h"
+#include "Firestore/core/src/firebase/firestore/model/no_document.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+#include "Firestore/core/src/firebase/firestore/nanopb/reader.h"
+#include "Firestore/core/src/firebase/firestore/nanopb/tag.h"
+#include "Firestore/core/src/firebase/firestore/nanopb/writer.h"
+#include "Firestore/core/src/firebase/firestore/timestamp_internal.h"
#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "absl/memory/memory.h"
namespace firebase {
namespace firestore {
namespace remote {
+using firebase::Timestamp;
+using firebase::TimestampInternal;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::Document;
+using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::FieldValue;
+using firebase::firestore::model::MaybeDocument;
+using firebase::firestore::model::NoDocument;
using firebase::firestore::model::ObjectValue;
+using firebase::firestore::model::ResourcePath;
+using firebase::firestore::model::SnapshotVersion;
+using firebase::firestore::nanopb::Reader;
+using firebase::firestore::nanopb::Tag;
+using firebase::firestore::nanopb::Writer;
using firebase::firestore::util::Status;
+using firebase::firestore::util::StatusOr;
namespace {
-class Writer;
-
-class Reader;
-
void EncodeObject(Writer* writer, const ObjectValue& object_value);
-ObjectValue DecodeObject(Reader* reader);
-
-/**
- * Represents a nanopb tag.
- *
- * field_number is one of the field tags that nanopb generates based off of
- * the proto messages. They're typically named in the format:
- * <parentNameSpace>_<childNameSpace>_<message>_<field>_tag, e.g.
- * google_firestore_v1beta1_Document_name_tag.
- */
-struct Tag {
- pb_wire_type_t wire_type;
- uint32_t field_number;
-};
+ObjectValue::Map DecodeObject(Reader* reader);
-/**
- * Docs TODO(rsgowman). But currently, this just wraps the underlying nanopb
- * pb_ostream_t. Also doc how to check status.
- */
-class Writer {
- public:
- /**
- * Creates an output stream that writes to the specified vector. Note that
- * this vector pointer must remain valid for the lifetime of this Writer.
- *
- * (This is roughly equivalent to the nanopb function
- * pb_ostream_from_buffer())
- *
- * @param out_bytes where the output should be serialized to.
- */
- static Writer Wrap(std::vector<uint8_t>* out_bytes);
-
- /**
- * Creates a non-writing output stream used to calculate the size of
- * the serialized output.
- */
- static Writer Sizing() {
- return Writer(PB_OSTREAM_SIZING);
- }
-
- /**
- * Writes a message type to the output stream.
- *
- * This essentially wraps calls to nanopb's pb_encode_tag() method.
- */
- void WriteTag(Tag tag);
-
- void WriteSize(size_t size);
- void WriteNull();
- void WriteBool(bool bool_value);
- void WriteInteger(int64_t integer_value);
-
- void WriteString(const std::string& string_value);
-
- /**
- * Writes a message and its length.
- *
- * When writing a top level message, protobuf doesn't include the length
- * (since you can get that already from the length of the binary output.) But
- * when writing a sub/nested message, you must include the length in the
- * serialization.
- *
- * Call this method when writing a nested message. Provide a function to
- * write the message itself. This method will calculate the size of the
- * written message (using the provided function with a non-writing sizing
- * stream), write out the size (and perform sanity checks), and then serialize
- * the message by calling the provided function a second time.
- */
- void WriteNestedMessage(const std::function<void(Writer*)>& write_message_fn);
-
- size_t bytes_written() const {
- return stream_.bytes_written;
- }
-
- Status status() const {
- return status_;
- }
-
- private:
- Status status_ = Status::OK();
-
- /**
- * Creates a new Writer, based on the given nanopb pb_ostream_t. Note that
- * a shallow copy will be taken. (Non-null pointers within this struct must
- * remain valid for the lifetime of this Writer.)
- */
- explicit Writer(const pb_ostream_t& stream) : stream_(stream) {
- }
-
- /**
- * Writes a "varint" to the output stream.
- *
- * This essentially wraps calls to nanopb's pb_encode_varint() method.
- *
- * Note that (despite the value parameter type) this works for bool, enum,
- * int32, int64, uint32 and uint64 proto field types.
- *
- * Note: This is not expected to be called directly, but rather only
- * via the other Write* methods (i.e. WriteBool, WriteLong, etc)
- *
- * @param value The value to write, represented as a uint64_t.
- */
- void WriteVarint(uint64_t value);
-
- pb_ostream_t stream_;
-};
-
-/**
- * Docs TODO(rsgowman). But currently, this just wraps the underlying nanopb
- * pb_istream_t.
- */
-class Reader {
- public:
- /**
- * Creates an input stream that reads from the specified bytes. Note that
- * this reference must remain valid for the lifetime of this Reader.
- *
- * (This is roughly equivalent to the nanopb function
- * pb_istream_from_buffer())
- *
- * @param bytes where the input should be deserialized from.
- */
- static Reader Wrap(const uint8_t* bytes, size_t length);
-
- /**
- * Reads a message type from the input stream.
- *
- * This essentially wraps calls to nanopb's pb_decode_tag() method.
- */
- Tag ReadTag();
-
- void ReadNull();
- bool ReadBool();
- int64_t ReadInteger();
-
- std::string ReadString();
-
- /**
- * Reads a message and its length.
- *
- * Analog to Writer::WriteNestedMessage(). See that methods docs for further
- * details.
- *
- * Call this method when reading a nested message. Provide a function to read
- * the message itself.
- */
- template <typename T>
- T ReadNestedMessage(const std::function<T(Reader*)>& read_message_fn);
-
- size_t bytes_left() const {
- return stream_.bytes_left;
- }
-
- private:
- /**
- * Creates a new Reader, based on the given nanopb pb_istream_t. Note that
- * a shallow copy will be taken. (Non-null pointers within this struct must
- * remain valid for the lifetime of this Reader.)
- */
- explicit Reader(pb_istream_t stream) : stream_(stream) {
- }
-
- /**
- * Reads a "varint" from the input stream.
- *
- * This essentially wraps calls to nanopb's pb_decode_varint() method.
- *
- * Note that (despite the return type) this works for bool, enum, int32,
- * int64, uint32 and uint64 proto field types.
- *
- * Note: This is not expected to be called direclty, but rather only via the
- * other Decode* methods (i.e. DecodeBool, DecodeLong, etc)
- *
- * @return The decoded varint as a uint64_t.
- */
- uint64_t ReadVarint();
-
- pb_istream_t stream_;
-};
-
-Writer Writer::Wrap(std::vector<uint8_t>* out_bytes) {
- // TODO(rsgowman): find a better home for this constant.
- // A document is defined to have a max size of 1MiB - 4 bytes.
- static const size_t kMaxDocumentSize = 1 * 1024 * 1024 - 4;
-
- // Construct a nanopb output stream.
- //
- // Set the max_size to be the max document size (as an upper bound; one would
- // expect individual FieldValue's to be smaller than this).
- //
- // bytes_written is (always) initialized to 0. (NB: nanopb does not know or
- // care about the underlying output vector, so where we are in the vector
- // itself is irrelevant. i.e. don't use out_bytes->size())
- pb_ostream_t raw_stream = {
- /*callback=*/[](pb_ostream_t* stream, const pb_byte_t* buf,
- size_t count) -> bool {
- auto* out_bytes = static_cast<std::vector<uint8_t>*>(stream->state);
- out_bytes->insert(out_bytes->end(), buf, buf + count);
- return true;
- },
- /*state=*/out_bytes,
- /*max_size=*/kMaxDocumentSize,
- /*bytes_written=*/0,
- /*errmsg=*/nullptr};
- return Writer(raw_stream);
-}
-
-Reader Reader::Wrap(const uint8_t* bytes, size_t length) {
- return Reader{pb_istream_from_buffer(bytes, length)};
-}
-
-// TODO(rsgowman): I've left the methods as near as possible to where they were
-// before, which implies that the Writer methods are interspersed with the
-// Reader methods. This should make it a bit easier to review. Refactor these to
-// group the related methods together (probably within their own file rather
-// than here).
-
-void Writer::WriteTag(Tag tag) {
- if (!status_.ok()) return;
-
- if (!pb_encode_tag(&stream_, tag.wire_type, tag.field_number)) {
- FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
- }
+void EncodeTimestamp(Writer* writer, const Timestamp& timestamp_value) {
+ google_protobuf_Timestamp timestamp_proto =
+ google_protobuf_Timestamp_init_zero;
+ timestamp_proto.seconds = timestamp_value.seconds();
+ timestamp_proto.nanos = timestamp_value.nanoseconds();
+ writer->WriteNanopbMessage(google_protobuf_Timestamp_fields,
+ &timestamp_proto);
}
-Tag Reader::ReadTag() {
- Tag tag;
- bool eof;
- bool ok = pb_decode_tag(&stream_, &tag.wire_type, &tag.field_number, &eof);
- if (!ok || eof) {
- // TODO(rsgowman): figure out error handling
- abort();
+Timestamp DecodeTimestamp(Reader* reader) {
+ if (!reader->status().ok()) return {};
+
+ google_protobuf_Timestamp timestamp_proto =
+ google_protobuf_Timestamp_init_zero;
+ reader->ReadNanopbMessage(google_protobuf_Timestamp_fields, &timestamp_proto);
+
+ // The Timestamp ctor will assert if we provide values outside the valid
+ // range. However, since we're decoding, a single corrupt byte could cause
+ // this to occur, so we'll verify the ranges before passing them in since we'd
+ // rather not abort in these situations.
+ if (timestamp_proto.seconds < TimestampInternal::Min().seconds()) {
+ reader->set_status(Status(
+ FirestoreErrorCode::DataLoss,
+ "Invalid message: timestamp beyond the earliest supported date"));
+ return {};
+ } else if (TimestampInternal::Max().seconds() < timestamp_proto.seconds) {
+ reader->set_status(
+ Status(FirestoreErrorCode::DataLoss,
+ "Invalid message: timestamp behond the latest supported date"));
+ return {};
+ } else if (timestamp_proto.nanos < 0 || timestamp_proto.nanos > 999999999) {
+ reader->set_status(Status(
+ FirestoreErrorCode::DataLoss,
+ "Invalid message: timestamp nanos must be between 0 and 999999999"));
+ return {};
}
- return tag;
-}
-
-void Writer::WriteSize(size_t size) {
- return WriteVarint(size);
-}
-
-void Writer::WriteVarint(uint64_t value) {
- if (!status_.ok()) return;
-
- if (!pb_encode_varint(&stream_, value)) {
- FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
- }
-}
-
-/**
- * Note that (despite the return type) this works for bool, enum, int32, int64,
- * uint32 and uint64 proto field types.
- *
- * Note: This is not expected to be called directly, but rather only via the
- * other Decode* methods (i.e. DecodeBool, DecodeLong, etc)
- *
- * @return The decoded varint as a uint64_t.
- */
-uint64_t Reader::ReadVarint() {
- uint64_t varint_value;
- if (!pb_decode_varint(&stream_, &varint_value)) {
- // TODO(rsgowman): figure out error handling
- abort();
- }
- return varint_value;
-}
-
-void Writer::WriteNull() {
- return WriteVarint(google_protobuf_NullValue_NULL_VALUE);
-}
-
-void Reader::ReadNull() {
- uint64_t varint = ReadVarint();
- if (varint != google_protobuf_NullValue_NULL_VALUE) {
- // TODO(rsgowman): figure out error handling
- abort();
- }
-}
-
-void Writer::WriteBool(bool bool_value) {
- return WriteVarint(bool_value);
-}
-
-bool Reader::ReadBool() {
- uint64_t varint = ReadVarint();
- switch (varint) {
- case 0:
- return false;
- case 1:
- return true;
- default:
- // TODO(rsgowman): figure out error handling
- abort();
- }
-}
-
-void Writer::WriteInteger(int64_t integer_value) {
- return WriteVarint(integer_value);
-}
-
-int64_t Reader::ReadInteger() {
- return ReadVarint();
-}
-
-void Writer::WriteString(const std::string& string_value) {
- if (!status_.ok()) return;
-
- if (!pb_encode_string(
- &stream_, reinterpret_cast<const pb_byte_t*>(string_value.c_str()),
- string_value.length())) {
- FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
- }
-}
-
-std::string Reader::ReadString() {
- pb_istream_t substream;
- if (!pb_make_string_substream(&stream_, &substream)) {
- // TODO(rsgowman): figure out error handling
- abort();
- }
-
- std::string result(substream.bytes_left, '\0');
- if (!pb_read(&substream, reinterpret_cast<pb_byte_t*>(&result[0]),
- substream.bytes_left)) {
- // TODO(rsgowman): figure out error handling
- abort();
- }
-
- // NB: future versions of nanopb read the remaining characters out of the
- // substream (and return false if that fails) as an additional safety
- // check within pb_close_string_substream. Unfortunately, that's not present
- // in the current version (0.38). We'll make a stronger assertion and check
- // to make sure there *are* no remaining characters in the substream.
- if (substream.bytes_left != 0) {
- // TODO(rsgowman): figure out error handling
- abort();
- }
-
- pb_close_string_substream(&stream_, &substream);
-
- return result;
+ return Timestamp{timestamp_proto.seconds, timestamp_proto.nanos};
}
// Named '..Impl' so as to not conflict with Serializer::EncodeFieldValue.
@@ -417,6 +137,14 @@ void EncodeFieldValueImpl(Writer* writer, const FieldValue& field_value) {
writer->WriteString(field_value.string_value());
break;
+ case FieldValue::Type::Timestamp:
+ writer->WriteTag(
+ {PB_WT_STRING, google_firestore_v1beta1_Value_timestamp_value_tag});
+ writer->WriteNestedMessage([&field_value](Writer* writer) {
+ EncodeTimestamp(writer, field_value.timestamp_value());
+ });
+ break;
+
case FieldValue::Type::Object:
writer->WriteTag(
{PB_WT_STRING, google_firestore_v1beta1_Value_map_value_tag});
@@ -430,30 +158,56 @@ void EncodeFieldValueImpl(Writer* writer, const FieldValue& field_value) {
}
FieldValue DecodeFieldValueImpl(Reader* reader) {
+ if (!reader->status().ok()) return FieldValue::NullValue();
+
Tag tag = reader->ReadTag();
+ if (!reader->status().ok()) return FieldValue::NullValue();
// Ensure the tag matches the wire type
- // TODO(rsgowman): figure out error handling
switch (tag.field_number) {
case google_firestore_v1beta1_Value_null_value_tag:
case google_firestore_v1beta1_Value_boolean_value_tag:
case google_firestore_v1beta1_Value_integer_value_tag:
if (tag.wire_type != PB_WT_VARINT) {
- abort();
+ reader->set_status(
+ Status(FirestoreErrorCode::DataLoss,
+ "Input proto bytes cannot be parsed (mismatch between "
+ "the wiretype and the field number (tag))"));
}
break;
case google_firestore_v1beta1_Value_string_value_tag:
+ case google_firestore_v1beta1_Value_timestamp_value_tag:
case google_firestore_v1beta1_Value_map_value_tag:
if (tag.wire_type != PB_WT_STRING) {
- abort();
+ reader->set_status(
+ Status(FirestoreErrorCode::DataLoss,
+ "Input proto bytes cannot be parsed (mismatch between "
+ "the wiretype and the field number (tag))"));
}
break;
default:
- abort();
+ // We could get here for one of two reasons; either because the input
+ // bytes are corrupt, or because we're attempting to parse a tag that we
+ // haven't implemented yet. Long term, the latter reason should become
+ // less likely (especially in production), so we'll assume former.
+
+ // TODO(rsgowman): While still in development, we'll contradict the above
+ // and assume the latter. Remove the following assertion when we're
+ // confident that we're handling all the tags in the protos.
+ FIREBASE_ASSERT_MESSAGE(
+ false,
+ "Unhandled message field number (tag): %i. (Or possibly "
+ "corrupt input bytes)",
+ tag.field_number);
+ reader->set_status(Status(
+ FirestoreErrorCode::DataLoss,
+ "Input proto bytes cannot be parsed (invalid field number (tag))"));
}
+ if (!reader->status().ok()) return FieldValue::NullValue();
+
switch (tag.field_number) {
case google_firestore_v1beta1_Value_null_value_tag:
reader->ReadNull();
@@ -464,98 +218,22 @@ FieldValue DecodeFieldValueImpl(Reader* reader) {
return FieldValue::IntegerValue(reader->ReadInteger());
case google_firestore_v1beta1_Value_string_value_tag:
return FieldValue::StringValue(reader->ReadString());
+ case google_firestore_v1beta1_Value_timestamp_value_tag:
+ return FieldValue::TimestampValue(
+ reader->ReadNestedMessage<Timestamp>(DecodeTimestamp));
case google_firestore_v1beta1_Value_map_value_tag:
- return FieldValue::ObjectValueFromMap(
- DecodeObject(reader).internal_value);
+ return FieldValue::ObjectValueFromMap(DecodeObject(reader));
default:
- // TODO(rsgowman): figure out error handling
- abort();
+ // This indicates an internal error as we've already ensured that this is
+ // a valid field_number.
+ FIREBASE_ASSERT_MESSAGE(
+ false,
+ "Somehow got an unexpected field number (tag) after verifying that "
+ "the field number was expected.");
}
}
-void Writer::WriteNestedMessage(
- const std::function<void(Writer*)>& write_message_fn) {
- if (!status_.ok()) return;
-
- // First calculate the message size using a non-writing substream.
- Writer sizer = Writer::Sizing();
- write_message_fn(&sizer);
- status_ = sizer.status();
- if (!status_.ok()) return;
- size_t size = sizer.bytes_written();
-
- // Write out the size to the output writer.
- WriteSize(size);
- if (!status_.ok()) return;
-
- // If this stream is itself a sizing stream, then we don't need to actually
- // parse field_value a second time; just update the bytes_written via a call
- // to pb_write. (If we try to write the contents into a sizing stream, it'll
- // fail since sizing streams don't actually have any buffer space.)
- if (stream_.callback == nullptr) {
- if (!pb_write(&stream_, nullptr, size)) {
- FIREBASE_ASSERT_MESSAGE(false, PB_GET_ERROR(&stream_));
- }
- return;
- }
-
- // Ensure the output stream has enough space
- if (stream_.bytes_written + size > stream_.max_size) {
- FIREBASE_ASSERT_MESSAGE(
- false,
- "Insufficient space in the output stream to write the given message");
- }
-
- // Use a substream to verify that a callback doesn't write more than what it
- // did the first time. (Use an initializer rather than setting fields
- // individually like nanopb does. This gives us a *chance* of noticing if
- // nanopb adds new fields.)
- Writer writer({stream_.callback, stream_.state,
- /*max_size=*/size, /*bytes_written=*/0,
- /*errmsg=*/nullptr});
- write_message_fn(&writer);
- status_ = writer.status();
- if (!status_.ok()) return;
-
- stream_.bytes_written += writer.stream_.bytes_written;
- stream_.state = writer.stream_.state;
- stream_.errmsg = writer.stream_.errmsg;
-
- if (writer.bytes_written() != size) {
- // submsg size changed
- FIREBASE_ASSERT_MESSAGE(
- false, "Parsing the nested message twice yielded different sizes");
- }
-}
-
-template <typename T>
-T Reader::ReadNestedMessage(const std::function<T(Reader*)>& read_message_fn) {
- // Implementation note: This is roughly modeled on pb_decode_delimited,
- // adjusted to account for the oneof in FieldValue.
- pb_istream_t raw_substream;
- if (!pb_make_string_substream(&stream_, &raw_substream)) {
- // TODO(rsgowman): figure out error handling
- abort();
- }
- Reader substream(raw_substream);
-
- T message = read_message_fn(&substream);
-
- // NB: future versions of nanopb read the remaining characters out of the
- // substream (and return false if that fails) as an additional safety
- // check within pb_close_string_substream. Unfortunately, that's not present
- // in the current version (0.38). We'll make a stronger assertion and check
- // to make sure there *are* no remaining characters in the substream.
- if (substream.bytes_left() != 0) {
- // TODO(rsgowman): figure out error handling
- abort();
- }
- pb_close_string_substream(&stream_, &substream.stream_);
-
- return message;
-}
-
/**
* Encodes a 'FieldsEntry' object, within a FieldValue's map_value type.
*
@@ -590,7 +268,10 @@ void EncodeFieldsEntry(Writer* writer, const ObjectValue::Map::value_type& kv) {
}
ObjectValue::Map::value_type DecodeFieldsEntry(Reader* reader) {
+ if (!reader->status().ok()) return {};
+
Tag tag = reader->ReadTag();
+ if (!reader->status().ok()) return {};
// TODO(rsgowman): figure out error handling: We can do better than a failed
// assertion.
@@ -600,6 +281,7 @@ ObjectValue::Map::value_type DecodeFieldsEntry(Reader* reader) {
std::string key = reader->ReadString();
tag = reader->ReadTag();
+ if (!reader->status().ok()) return {};
FIREBASE_ASSERT(tag.field_number ==
google_firestore_v1beta1_MapValue_FieldsEntry_value_tag);
FIREBASE_ASSERT(tag.wire_type == PB_WT_STRING);
@@ -607,27 +289,32 @@ ObjectValue::Map::value_type DecodeFieldsEntry(Reader* reader) {
FieldValue value =
reader->ReadNestedMessage<FieldValue>(DecodeFieldValueImpl);
- return {key, value};
+ return ObjectValue::Map::value_type{key, value};
}
void EncodeObject(Writer* writer, const ObjectValue& object_value) {
return writer->WriteNestedMessage([&object_value](Writer* writer) {
// Write each FieldsEntry (i.e. key-value pair.)
for (const auto& kv : object_value.internal_value) {
- writer->WriteTag({PB_WT_STRING,
- google_firestore_v1beta1_MapValue_FieldsEntry_key_tag});
+ writer->WriteTag(
+ {PB_WT_STRING, google_firestore_v1beta1_MapValue_fields_tag});
writer->WriteNestedMessage(
[&kv](Writer* writer) { return EncodeFieldsEntry(writer, kv); });
}
});
}
-ObjectValue DecodeObject(Reader* reader) {
- ObjectValue::Map internal_value = reader->ReadNestedMessage<ObjectValue::Map>(
+ObjectValue::Map DecodeObject(Reader* reader) {
+ if (!reader->status().ok()) return ObjectValue::Map();
+
+ return reader->ReadNestedMessage<ObjectValue::Map>(
[](Reader* reader) -> ObjectValue::Map {
ObjectValue::Map result;
+ if (!reader->status().ok()) return result;
+
while (reader->bytes_left()) {
Tag tag = reader->ReadTag();
+ if (!reader->status().ok()) return result;
FIREBASE_ASSERT(tag.field_number ==
google_firestore_v1beta1_MapValue_fields_tag);
FIREBASE_ASSERT(tag.wire_type == PB_WT_STRING);
@@ -640,6 +327,7 @@ ObjectValue DecodeObject(Reader* reader) {
// map.
// TODO(rsgowman): figure out error handling: We can do better than a
// failed assertion.
+ if (!reader->status().ok()) return result;
FIREBASE_ASSERT(result.find(fv.first) == result.end());
// Add this key,fieldvalue to the results map.
@@ -647,7 +335,64 @@ ObjectValue DecodeObject(Reader* reader) {
}
return result;
});
- return ObjectValue{internal_value};
+}
+
+/**
+ * Creates the prefix for a fully qualified resource path, without a local path
+ * on the end.
+ */
+ResourcePath EncodeDatabaseId(const DatabaseId& database_id) {
+ return ResourcePath{"projects", database_id.project_id(), "databases",
+ database_id.database_id()};
+}
+
+/**
+ * Encodes a databaseId and resource path into the following form:
+ * /projects/$projectId/database/$databaseId/documents/$path
+ */
+std::string EncodeResourceName(const DatabaseId& database_id,
+ const ResourcePath& path) {
+ return EncodeDatabaseId(database_id)
+ .Append("documents")
+ .Append(path)
+ .CanonicalString();
+}
+
+/**
+ * Validates that a path has a prefix that looks like a valid encoded
+ * databaseId.
+ */
+bool IsValidResourceName(const ResourcePath& path) {
+ // Resource names have at least 4 components (project ID, database ID)
+ // and commonly the (root) resource type, e.g. documents
+ return path.size() >= 4 && path[0] == "projects" && path[2] == "databases";
+}
+
+/**
+ * Decodes a fully qualified resource name into a resource path and validates
+ * that there is a project and database encoded in the path. There are no
+ * guarantees that a local path is also encoded in this resource name.
+ */
+ResourcePath DecodeResourceName(absl::string_view encoded) {
+ ResourcePath resource = ResourcePath::FromString(encoded);
+ FIREBASE_ASSERT_MESSAGE(IsValidResourceName(resource),
+ "Tried to deserialize invalid key %s",
+ resource.CanonicalString().c_str());
+ return resource;
+}
+
+/**
+ * Decodes a fully qualified resource name into a resource path and validates
+ * that there is a project and database encoded in the path along with a local
+ * path.
+ */
+ResourcePath ExtractLocalPathFromResourceName(
+ const ResourcePath& resource_name) {
+ FIREBASE_ASSERT_MESSAGE(
+ resource_name.size() > 4 && resource_name[4] == "documents",
+ "Tried to deserialize invalid key %s",
+ resource_name.CanonicalString().c_str());
+ return resource_name.PopFirst(5);
}
} // namespace
@@ -659,9 +404,157 @@ Status Serializer::EncodeFieldValue(const FieldValue& field_value,
return writer.status();
}
-FieldValue Serializer::DecodeFieldValue(const uint8_t* bytes, size_t length) {
+StatusOr<FieldValue> Serializer::DecodeFieldValue(const uint8_t* bytes,
+ size_t length) {
Reader reader = Reader::Wrap(bytes, length);
- return DecodeFieldValueImpl(&reader);
+ FieldValue fv = DecodeFieldValueImpl(&reader);
+ if (reader.status().ok()) {
+ return fv;
+ } else {
+ return reader.status();
+ }
+}
+
+std::string Serializer::EncodeKey(const DocumentKey& key) const {
+ return EncodeResourceName(database_id_, key.path());
+}
+
+DocumentKey Serializer::DecodeKey(absl::string_view name) const {
+ ResourcePath resource = DecodeResourceName(name);
+ FIREBASE_ASSERT_MESSAGE(resource[1] == database_id_.project_id(),
+ "Tried to deserialize key from different project.");
+ FIREBASE_ASSERT_MESSAGE(resource[3] == database_id_.database_id(),
+ "Tried to deserialize key from different database.");
+ return DocumentKey{ExtractLocalPathFromResourceName(resource)};
+}
+
+util::Status Serializer::EncodeDocument(const DocumentKey& key,
+ const ObjectValue& value,
+ std::vector<uint8_t>* out_bytes) const {
+ Writer writer = Writer::Wrap(out_bytes);
+ EncodeDocument(&writer, key, value);
+ return writer.status();
+}
+
+void Serializer::EncodeDocument(Writer* writer,
+ const DocumentKey& key,
+ const ObjectValue& object_value) const {
+ // Encode Document.name
+ writer->WriteTag({PB_WT_STRING, google_firestore_v1beta1_Document_name_tag});
+ writer->WriteString(EncodeKey(key));
+
+ // Encode Document.fields (unless it's empty)
+ if (!object_value.internal_value.empty()) {
+ writer->WriteTag(
+ {PB_WT_STRING, google_firestore_v1beta1_Document_fields_tag});
+ EncodeObject(writer, object_value);
+ }
+
+ // Skip Document.create_time and Document.update_time, since they're
+ // output-only fields.
+}
+
+util::StatusOr<std::unique_ptr<model::MaybeDocument>>
+Serializer::DecodeMaybeDocument(const uint8_t* bytes, size_t length) const {
+ Reader reader = Reader::Wrap(bytes, length);
+ std::unique_ptr<MaybeDocument> maybeDoc =
+ DecodeBatchGetDocumentsResponse(&reader);
+
+ if (reader.status().ok()) {
+ return maybeDoc;
+ } else {
+ return reader.status();
+ }
+}
+
+std::unique_ptr<MaybeDocument> Serializer::DecodeBatchGetDocumentsResponse(
+ Reader* reader) const {
+ Tag tag = reader->ReadTag();
+ if (!reader->status().ok()) return nullptr;
+
+ // Ensure the tag matches the wire type
+ switch (tag.field_number) {
+ case google_firestore_v1beta1_BatchGetDocumentsResponse_found_tag:
+ case google_firestore_v1beta1_BatchGetDocumentsResponse_missing_tag:
+ if (tag.wire_type != PB_WT_STRING) {
+ reader->set_status(
+ Status(FirestoreErrorCode::DataLoss,
+ "Input proto bytes cannot be parsed (mismatch between "
+ "the wiretype and the field number (tag))"));
+ }
+ break;
+
+ default:
+ reader->set_status(Status(
+ FirestoreErrorCode::DataLoss,
+ "Input proto bytes cannot be parsed (invalid field number (tag))"));
+ }
+
+ if (!reader->status().ok()) return nullptr;
+
+ switch (tag.field_number) {
+ case google_firestore_v1beta1_BatchGetDocumentsResponse_found_tag:
+ return reader->ReadNestedMessage<std::unique_ptr<MaybeDocument>>(
+ [this](Reader* reader) -> std::unique_ptr<MaybeDocument> {
+ return DecodeDocument(reader);
+ });
+ case google_firestore_v1beta1_BatchGetDocumentsResponse_missing_tag:
+ // TODO(rsgowman): Right now, we only support Document (and don't support
+ // NoDocument). That should change in the next PR or so.
+ abort();
+ default:
+ // This indicates an internal error as we've already ensured that this is
+ // a valid field_number.
+ FIREBASE_ASSERT_MESSAGE(
+ false,
+ "Somehow got an unexpected field number (tag) after verifying that "
+ "the field number was expected.");
+ }
+}
+
+std::unique_ptr<Document> Serializer::DecodeDocument(Reader* reader) const {
+ if (!reader->status().ok()) return nullptr;
+
+ std::string name;
+ FieldValue fields = FieldValue::ObjectValueFromMap({});
+ SnapshotVersion version = SnapshotVersion::None();
+
+ while (reader->bytes_left()) {
+ Tag tag = reader->ReadTag();
+ if (!reader->status().ok()) return nullptr;
+ FIREBASE_ASSERT(tag.wire_type == PB_WT_STRING);
+ switch (tag.field_number) {
+ case google_firestore_v1beta1_Document_name_tag:
+ name = reader->ReadString();
+ break;
+ case google_firestore_v1beta1_Document_fields_tag:
+ // TODO(rsgowman): Rather than overwriting, we should instead merge with
+ // the existing FieldValue (if any).
+ fields = DecodeFieldValueImpl(reader);
+ break;
+ case google_firestore_v1beta1_Document_create_time_tag:
+ // This field is ignored by the client sdk, but we still need to extract
+ // it.
+ reader->ReadNestedMessage<Timestamp>(DecodeTimestamp);
+ break;
+ case google_firestore_v1beta1_Document_update_time_tag:
+ // TODO(rsgowman): Rather than overwriting, we should instead merge with
+ // the existing SnapshotVersion (if any). Less relevant here, since it's
+ // just two numbers which are both expected to be present, but if the
+ // proto evolves that might change.
+ version = SnapshotVersion{
+ reader->ReadNestedMessage<Timestamp>(DecodeTimestamp)};
+ break;
+ default:
+ // TODO(rsgowman): Error handling. (Invalid tags should fail to decode,
+ // but shouldn't cause a crash.)
+ abort();
+ }
+ }
+
+ return absl::make_unique<Document>(std::move(fields), DecodeKey(name),
+ version,
+ /*has_local_modifications=*/false);
}
} // namespace remote
diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.h b/Firestore/core/src/firebase/firestore/remote/serializer.h
index 7f08f7d..e79c238 100644
--- a/Firestore/core/src/firebase/firestore/remote/serializer.h
+++ b/Firestore/core/src/firebase/firestore/remote/serializer.h
@@ -19,12 +19,22 @@
#include <cstdint>
#include <cstdlib>
+#include <memory>
+#include <string>
#include <vector>
+#include "Firestore/core/src/firebase/firestore/model/database_id.h"
+#include "Firestore/core/src/firebase/firestore/model/document.h"
+#include "Firestore/core/src/firebase/firestore/model/document_key.h"
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+#include "Firestore/core/src/firebase/firestore/model/maybe_document.h"
+#include "Firestore/core/src/firebase/firestore/nanopb/reader.h"
+#include "Firestore/core/src/firebase/firestore/nanopb/writer.h"
#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
#include "Firestore/core/src/firebase/firestore/util/status.h"
+#include "Firestore/core/src/firebase/firestore/util/statusor.h"
#include "absl/base/attributes.h"
+#include "absl/strings/string_view.h"
namespace firebase {
namespace firestore {
@@ -45,29 +55,23 @@ namespace remote {
// interpret." Adjust for C++.
class Serializer {
public:
- Serializer() {
+ /**
+ * @param database_id Must remain valid for the lifetime of this Serializer
+ * object.
+ */
+ explicit Serializer(const firebase::firestore::model::DatabaseId& database_id)
+ : database_id_(database_id) {
}
- // TODO(rsgowman): We eventually need the DatabaseId, but can't add it just
- // yet since it's not used yet (which travis complains about). So for now,
- // we'll create a parameterless ctor (above) that likely won't exist in the
- // final version of this class.
- ///**
- // * @param database_id Must remain valid for the lifetime of this Serializer
- // * object.
- // */
- // explicit Serializer(const firebase::firestore::model::DatabaseId&
- // database_id)
- // : database_id_(database_id) {
- //}
/**
- * Converts the FieldValue model passed into bytes.
+ * @brief Converts the FieldValue model passed into bytes.
*
* @param field_value the model to convert.
* @param[out] out_bytes A buffer to place the output. The bytes will be
* appended to this vector.
+ * @return A Status, which if not ok(), indicates what went wrong. Note that
+ * errors during encoding generally indicate a serious/fatal error.
*/
- // TODO(rsgowman): error handling, incl return code.
// TODO(rsgowman): If we never support any output except to a vector, it may
// make sense to have Serializer own the vector and provide an accessor rather
// than asking the user to create it first.
@@ -80,26 +84,89 @@ class Serializer {
*
* @param bytes The bytes to convert. It's assumed that exactly all of the
* bytes will be used by this conversion.
- * @return The model equivalent of the bytes.
+ * @return The model equivalent of the bytes or a Status indicating
+ * what went wrong.
*/
- // TODO(rsgowman): error handling.
- model::FieldValue DecodeFieldValue(const uint8_t* bytes, size_t length);
+ util::StatusOr<model::FieldValue> DecodeFieldValue(const uint8_t* bytes,
+ size_t length);
/**
* @brief Converts from bytes to the model FieldValue format.
*
* @param bytes The bytes to convert. It's assumed that exactly all of the
* bytes will be used by this conversion.
- * @return The model equivalent of the bytes.
+ * @return The model equivalent of the bytes or a Status indicating
+ * what went wrong.
*/
- // TODO(rsgowman): error handling.
- model::FieldValue DecodeFieldValue(const std::vector<uint8_t>& bytes) {
+ util::StatusOr<model::FieldValue> DecodeFieldValue(
+ const std::vector<uint8_t>& bytes) {
return DecodeFieldValue(bytes.data(), bytes.size());
}
+ /**
+ * Encodes the given document key as a fully qualified name. This includes the
+ * databaseId associated with this Serializer and the key path.
+ */
+ std::string EncodeKey(
+ const firebase::firestore::model::DocumentKey& key) const;
+
+ /**
+ * Decodes the given document key from a fully qualified name.
+ */
+ firebase::firestore::model::DocumentKey DecodeKey(
+ absl::string_view name) const;
+
+ /**
+ * @brief Converts the Document (i.e. key/value) into bytes.
+ *
+ * @param[out] out_bytes A buffer to place the output. The bytes will be
+ * appended to this vector.
+ * @return A Status, which if not ok(), indicates what went wrong. Note that
+ * errors during encoding generally indicate a serious/fatal error.
+ */
+ // TODO(rsgowman): Similar to above, if we never support any output except to
+ // a vector, it may make sense to have Serializer own the vector and provide
+ // an accessor rather than asking the user to create it first.
+ util::Status EncodeDocument(
+ const firebase::firestore::model::DocumentKey& key,
+ const firebase::firestore::model::ObjectValue& value,
+ std::vector<uint8_t>* out_bytes) const;
+
+ /**
+ * @brief Converts from bytes to the model Document format.
+ *
+ * @param bytes The bytes to convert. These bytes must represent a
+ * BatchGetDocumentsResponse. It's assumed that exactly all of the bytes will
+ * be used by this conversion.
+ * @return The model equivalent of the bytes or a Status indicating
+ * what went wrong.
+ */
+ util::StatusOr<std::unique_ptr<model::MaybeDocument>> DecodeMaybeDocument(
+ const uint8_t* bytes, size_t length) const;
+
+ /**
+ * @brief Converts from bytes to the model Document format.
+ *
+ * @param bytes The bytes to convert. These bytes must represent a
+ * BatchGetDocumentsResponse. It's assumed that exactly all of the bytes will
+ * be used by this conversion.
+ * @return The model equivalent of the bytes or a Status indicating
+ * what went wrong.
+ */
+ util::StatusOr<std::unique_ptr<model::MaybeDocument>> DecodeMaybeDocument(
+ const std::vector<uint8_t>& bytes) const {
+ return DecodeMaybeDocument(bytes.data(), bytes.size());
+ }
+
private:
- // TODO(rsgowman): We don't need the database_id_ yet (but will eventually).
- // model::DatabaseId* database_id_;
+ void EncodeDocument(nanopb::Writer* writer,
+ const model::DocumentKey& key,
+ const model::ObjectValue& object_value) const;
+ std::unique_ptr<model::MaybeDocument> DecodeBatchGetDocumentsResponse(
+ nanopb::Reader* reader) const;
+ std::unique_ptr<model::Document> DecodeDocument(nanopb::Reader* reader) const;
+
+ const firebase::firestore::model::DatabaseId& database_id_;
};
} // namespace remote
diff --git a/Firestore/core/src/firebase/firestore/timestamp_internal.h b/Firestore/core/src/firebase/firestore/timestamp_internal.h
new file mode 100644
index 0000000..3516176
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/timestamp_internal.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_TIMESTAMP_INTERNAL_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_TIMESTAMP_INTERNAL_H_
+
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
+
+namespace firebase {
+
+/**
+ * Details about the Timestamp class which are useful internally, but we don't
+ * want to expose publicly.
+ */
+class TimestampInternal {
+ public:
+ /**
+ * Represents the maximum allowable time that the Timestamp class handles,
+ * specifically 9999-12-31T23:59:59.999999999Z.
+ */
+ static firebase::Timestamp Max() {
+ return {253402300800L - 1, 999999999};
+ }
+
+ /**
+ * Represents the minimum allowable time that the Timestamp class handles,
+ * specifically 0001-01-01T00:00:00Z.
+ */
+ static firebase::Timestamp Min() {
+ return {-62135596800L, 0};
+ }
+};
+
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_TIMESTAMP_INTERNAL_H_
diff --git a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt
index 3afead1..b2b015b 100644
--- a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt
+++ b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt
@@ -109,6 +109,63 @@ else()
endif()
+## async queue
+
+check_symbol_exists(dispatch_async_f dispatch/dispatch.h HAVE_LIBDISPATCH)
+
+cc_library(
+ firebase_firestore_util_executor_std
+ SOURCES
+ executor_std.cc
+ executor_std.h
+ executor.h
+ DEPENDS
+ absl_bad_optional_access
+ absl_optional
+ ${FIREBASE_FIRESTORE_UTIL_LOG}
+ EXCLUDE_FROM_ALL
+)
+
+if(HAVE_LIBDISPATCH)
+cc_library(
+ firebase_firestore_util_executor_libdispatch
+ SOURCES
+ executor_libdispatch.mm
+ executor_libdispatch.h
+ executor.h
+ DEPENDS
+ absl_bad_optional_access
+ absl_optional
+ absl_strings
+ ${FIREBASE_FIRESTORE_UTIL_LOG}
+ EXCLUDE_FROM_ALL
+)
+endif()
+
+if(HAVE_LIBDISPATCH)
+ set(
+ FIREBASE_FIRESTORE_UTIL_EXECUTOR
+ firebase_firestore_util_executor_libdispatch
+ )
+
+else()
+ set(
+ FIREBASE_FIRESTORE_UTIL_EXECUTOR
+ firebase_firestore_util_executor_std
+ )
+
+endif()
+
+cc_library(
+ firebase_firestore_util_async_queue
+ SOURCES
+ async_queue.cc
+ async_queue.h
+ DEPENDS
+ ${FIREBASE_FIRESTORE_UTIL_EXECUTOR}
+ ${FIREBASE_FIRESTORE_UTIL_LOG}
+ EXCLUDE_FROM_ALL
+)
## main library
@@ -128,6 +185,7 @@ cc_library(
comparison.cc
comparison.h
config.h
+ hashing.h
iterator_adaptors.h
ordered_code.cc
ordered_code.h
@@ -143,6 +201,7 @@ cc_library(
DEPENDS
absl_base
firebase_firestore_util_base
+ firebase_firestore_util_async_queue
${FIREBASE_FIRESTORE_UTIL_LOG}
${FIREBASE_FIRESTORE_UTIL_RANDOM}
)
diff --git a/Firestore/core/src/firebase/firestore/util/async_queue.cc b/Firestore/core/src/firebase/firestore/util/async_queue.cc
new file mode 100644
index 0000000..81aac7c
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/async_queue.cc
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/async_queue.h"
+
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "absl/memory/memory.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+using internal::Executor;
+
+AsyncQueue::AsyncQueue(std::unique_ptr<Executor> executor)
+ : executor_{std::move(executor)} {
+ is_operation_in_progress_ = false;
+}
+
+// TODO(varconst): assert in destructor that the queue is empty.
+
+void AsyncQueue::VerifyIsCurrentExecutor() const {
+ FIREBASE_ASSERT_MESSAGE(
+ executor_->IsCurrentExecutor(),
+ "Expected to be called by the executor associated with this queue "
+ "(expected executor: '%s', actual executor: '%s')",
+ executor_->Name().c_str(), executor_->CurrentExecutorName().c_str());
+}
+
+void AsyncQueue::VerifyIsCurrentQueue() const {
+ VerifyIsCurrentExecutor();
+ FIREBASE_ASSERT_MESSAGE(
+ is_operation_in_progress_,
+ "VerifyIsCurrentQueue called when no operation is executing "
+ "(expected executor: '%s', actual executor: '%s')",
+ executor_->Name().c_str(), executor_->CurrentExecutorName().c_str());
+}
+
+void AsyncQueue::ExecuteBlocking(const Operation& operation) {
+ VerifyIsCurrentExecutor();
+ FIREBASE_ASSERT_MESSAGE(!is_operation_in_progress_,
+ "ExecuteBlocking may not be called "
+ "before the previous operation finishes executing");
+
+ is_operation_in_progress_ = true;
+ operation();
+ is_operation_in_progress_ = false;
+}
+
+void AsyncQueue::Enqueue(const Operation& operation) {
+ VerifySequentialOrder();
+ EnqueueRelaxed(operation);
+}
+
+void AsyncQueue::EnqueueRelaxed(const Operation& operation) {
+ executor_->Execute(Wrap(operation));
+}
+
+DelayedOperation AsyncQueue::EnqueueAfterDelay(const Milliseconds delay,
+ const TimerId timer_id,
+ const Operation& operation) {
+ VerifyIsCurrentExecutor();
+
+ // While not necessarily harmful, we currently don't expect to have multiple
+ // callbacks with the same timer_id in the queue, so defensively reject
+ // them.
+ FIREBASE_ASSERT_MESSAGE(
+ !IsScheduled(timer_id),
+ "Attempted to schedule multiple operations with id %d", timer_id);
+
+ Executor::TaggedOperation tagged{static_cast<int>(timer_id), Wrap(operation)};
+ return executor_->Schedule(delay, std::move(tagged));
+}
+
+AsyncQueue::Operation AsyncQueue::Wrap(const Operation& operation) {
+ // Decorator pattern: wrap `operation` into a call to `ExecuteBlocking` to
+ // ensure that it doesn't spawn any nested operations.
+
+ // Note: can't move `operation` into lambda until C++14.
+ return [this, operation] { ExecuteBlocking(operation); };
+}
+
+void AsyncQueue::VerifySequentialOrder() const {
+ // This is the inverse of `VerifyIsCurrentQueue`.
+ FIREBASE_ASSERT_MESSAGE(
+ !is_operation_in_progress_ || !executor_->IsCurrentExecutor(),
+ "Enqueue methods cannot be called when we are already running on "
+ "target executor"
+ "(this queue's executor: '%s', current executor: '%s')",
+ executor_->Name().c_str(), executor_->CurrentExecutorName().c_str());
+}
+
+// Test-only functions
+
+void AsyncQueue::EnqueueBlocking(const Operation& operation) {
+ VerifySequentialOrder();
+ executor_->ExecuteBlocking(Wrap(operation));
+}
+
+bool AsyncQueue::IsScheduled(const TimerId timer_id) const {
+ return executor_->IsScheduled(static_cast<int>(timer_id));
+}
+
+void AsyncQueue::RunScheduledOperationsUntil(const TimerId last_timer_id) {
+ FIREBASE_ASSERT_MESSAGE(
+ !executor_->IsCurrentExecutor(),
+ "RunScheduledOperationsUntil must not be called on the queue");
+
+ executor_->ExecuteBlocking([this, last_timer_id] {
+ FIREBASE_ASSERT_MESSAGE(
+ last_timer_id == TimerId::All || IsScheduled(last_timer_id),
+ "Attempted to run scheduled operations until missing timer id: %d",
+ last_timer_id);
+
+ for (auto next = executor_->PopFromSchedule(); next.has_value();
+ next = executor_->PopFromSchedule()) {
+ next->operation();
+ if (next->tag == static_cast<int>(last_timer_id)) {
+ break;
+ }
+ }
+ });
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/util/async_queue.h b/Firestore/core/src/firebase/firestore/util/async_queue.h
new file mode 100644
index 0000000..e2df387
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/async_queue.h
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_ASYNC_QUEUE_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_ASYNC_QUEUE_H_
+
+#include <atomic>
+#include <chrono> // NOLINT(build/c++11)
+#include <functional>
+#include <memory>
+
+#include "Firestore/core/src/firebase/firestore/util/executor.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+/**
+ * Well-known "timer" ids used when scheduling delayed operations on the
+ * AsyncQueue. These ids can then be used from tests to check for the
+ * presence of delayed operations or to run them early.
+ */
+enum class TimerId {
+ /** All can be used with `RunDelayedOperationsUntil` to run all timers. */
+ All,
+
+ /**
+ * The following 4 timers are used in `Stream` for the listen and write
+ * streams. The "Idle" timer is used to close the stream due to inactivity.
+ * The "ConnectionBackoff" timer is used to restart a stream once the
+ * appropriate backoff delay has elapsed.
+ */
+ ListenStreamIdle,
+ ListenStreamConnectionBackoff,
+ WriteStreamIdle,
+ WriteStreamConnectionBackoff,
+
+ /**
+ * A timer used in `OnlineStateTracker` to transition from
+ * `OnlineStateUnknown` to `Offline` after a set timeout, rather than waiting
+ * indefinitely for success or failure.
+ */
+ OnlineStateTimeout,
+};
+
+// A serial queue that executes given operations asynchronously, one at a time.
+// Operations may be scheduled to be executed as soon as possible or in the
+// future. Operations scheduled for the same time are FIFO-ordered.
+//
+// `AsyncQueue` wraps a platform-specific executor, adding checks that enforce
+// sequential ordering of operations: an enqueued operation, while being run,
+// normally cannot enqueue other operations for immediate execution (but see
+// `EnqueueRelaxed`).
+//
+// `AsyncQueue` methods have particular expectations about whether they must be
+// invoked on the queue or not; check "preconditions" section in comments on
+// each method.
+//
+// A significant portion of `AsyncQueue` interface only exists for test purposes
+// and must *not* be used in regular code.
+class AsyncQueue {
+ public:
+ using Operation = internal::Executor::Operation;
+ using Milliseconds = internal::Executor::Milliseconds;
+
+ explicit AsyncQueue(std::unique_ptr<internal::Executor> executor);
+
+ // Asserts for the caller that it is being invoked as part of an operation on
+ // the `AsyncQueue`.
+ void VerifyIsCurrentQueue() const;
+
+ // Enqueue methods
+
+ // Puts the `operation` on the queue to be executed as soon as possible, while
+ // maintaining FIFO order.
+ //
+ // Precondition: `Enqueue` calls cannot be nested; that is, `Enqueue` may not
+ // be called by a previously enqueued operation when it is run (as a special
+ // case, destructors invoked when an enqueued operation has run and is being
+ // destroyed may invoke `Enqueue`).
+ void Enqueue(const Operation& operation);
+
+ // Like `Enqueue`, but without applying any prerequisite checks.
+ void EnqueueRelaxed(const Operation& operation);
+
+ // Puts the `operation` on the queue to be executed `delay` milliseconds from
+ // now, and returns a handle that allows to cancel the operation (provided it
+ // hasn't run already).
+ //
+ // `operation` is tagged by a `timer_id` which allows to identify the caller.
+ // Only one operation tagged with any given `timer_id` may be on the queue at
+ // any time; an attempt to put another such operation will result in an
+ // assertion failure. In tests, these tags also allow to check for presence of
+ // certain operations and to run certain operations in advance.
+ //
+ // Precondition: `EnqueueAfterDelay` is being invoked asynchronously on the
+ // queue.
+ DelayedOperation EnqueueAfterDelay(Milliseconds delay,
+ TimerId timer_id,
+ const Operation& operation);
+
+ // Direct execution
+
+ // Immediately executes the `operation` on the queue.
+ //
+ // This is largely a workaround to allow other classes (GRPC) to directly
+ // access the underlying dispatch queue without getting `AsyncQueue` into an
+ // inconsistent state.
+ //
+ // Precondition: no other operation is being executed on the queue at the
+ // moment of the call (i.e., `ExecuteBlocking` cannot call `ExecuteBlocking`).
+ //
+ // Precondition: `ExecuteBlocking` is being invoked asynchronously on the
+ // queue.
+ void ExecuteBlocking(const Operation& operation);
+
+ // Test-only interface follows
+ // TODO(varconst): move the test-only interface into a helper object that is
+ // a friend of AsyncQueue and delegates its public methods to private methods
+ // on AsyncQueue.
+
+ // Like `Enqueue`, but blocks until the `operation` is complete.
+ void EnqueueBlocking(const Operation& operation);
+
+ // Checks whether an operation tagged with `timer_id` is currently scheduled
+ // for execution in the future.
+ bool IsScheduled(TimerId timer_id) const;
+
+ // Force runs operations scheduled for future execution, in scheduled order,
+ // up to *and including* the operation tagged with `last_timer_id`.
+ //
+ // Precondition: `RunScheduledOperationsUntil` is *not* being invoked on the
+ // queue.
+ void RunScheduledOperationsUntil(TimerId last_timer_id);
+
+ private:
+ Operation Wrap(const Operation& operation);
+
+ // Asserts that the current invocation happens asynchronously on the queue.
+ void VerifyIsCurrentExecutor() const;
+ void VerifySequentialOrder() const;
+
+ std::atomic<bool> is_operation_in_progress_;
+ std::unique_ptr<internal::Executor> executor_;
+};
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_ASYNC_QUEUE_H_
diff --git a/Firestore/core/src/firebase/firestore/util/executor.h b/Firestore/core/src/firebase/firestore/util/executor.h
new file mode 100644
index 0000000..df8b0b5
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/executor.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_EXECUTOR_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_EXECUTOR_H_
+
+#include <chrono> // NOLINT(build/c++11)
+#include <functional>
+#include <string>
+#include <utility>
+
+#include "absl/types/optional.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+// A handle to an operation scheduled for future execution. The handle may
+// outlive the operation, but it *cannot* outlive the executor that created it.
+class DelayedOperation {
+ public:
+ DelayedOperation() {
+ }
+
+ // If the operation has not been run yet, cancels the operation. Otherwise,
+ // this function is a no-op.
+ void Cancel() {
+ cancel_func_();
+ }
+
+ // Internal use only.
+ explicit DelayedOperation(std::function<void()>&& cancel_func)
+ : cancel_func_{std::move(cancel_func)} {
+ }
+
+ private:
+ std::function<void()> cancel_func_;
+};
+
+namespace internal {
+
+// An interface to a platform-specific executor of asynchronous operations
+// (called tasks on other platforms).
+//
+// Operations may be scheduled for immediate or delayed execution. Operations
+// delayed until the exact same time are scheduled in FIFO order.
+//
+// The operations are executed sequentially; only a single operation is executed
+// at any given time.
+//
+// Delayed operations may be canceled if they have not already been run.
+class Executor {
+ public:
+ using Tag = int;
+ using Operation = std::function<void()>;
+ using Milliseconds = std::chrono::milliseconds;
+
+ // Operations scheduled for future execution have an opaque tag. The value of
+ // the tag is ignored by the executor but can be used to find operations with
+ // a given tag after they are scheduled.
+ struct TaggedOperation {
+ TaggedOperation() {
+ }
+ TaggedOperation(const Tag tag, Operation&& operation)
+ : tag{tag}, operation{std::move(operation)} {
+ }
+ Tag tag = 0;
+ Operation operation;
+ };
+
+ virtual ~Executor() {
+ }
+
+ // Schedules the `operation` to be asynchronously executed as soon as
+ // possible, in FIFO order.
+ virtual void Execute(Operation&& operation) = 0;
+ // Like `Execute`, but blocks until the `operation` finishes, consequently
+ // draining immediate operations from the executor.
+ virtual void ExecuteBlocking(Operation&& operation) = 0;
+ // Scheduled the given `operation` to be executed after `delay` milliseconds
+ // from now, and returns a handle that allows to cancel the operation
+ // (provided it hasn't been run already). The operation is tagged to allow
+ // retrieving it later.
+ //
+ // `delay` must be non-negative; use `Execute` to schedule operations for
+ // immediate execution.
+ virtual DelayedOperation Schedule(Milliseconds delay,
+ TaggedOperation&& operation) = 0;
+
+ // Checks for the caller whether it is being invoked by this executor.
+ virtual bool IsCurrentExecutor() const = 0;
+ // Returns some sort of an identifier for the current execution context. The
+ // only guarantee is that it will return different values depending on whether
+ // this function is invoked by this executor or not.
+ virtual std::string CurrentExecutorName() const = 0;
+ // Like `CurrentExecutorName`, but returns an identifier for this executor,
+ // whether the caller code currently runs on this executor or not.
+ virtual std::string Name() const = 0;
+
+ // Checks whether an operation tagged with the given `tag` is currently
+ // scheduled for future execution.
+ virtual bool IsScheduled(Tag tag) const = 0;
+ // Removes the nearest due scheduled operation from the schedule and returns
+ // it to the caller. This function may be used to reschedule operations.
+ // Immediate operations don't count; only operations scheduled for delayed
+ // execution may be removed. If no such operations are currently scheduled, an
+ // empty `optional` is returned.
+ virtual absl::optional<TaggedOperation> PopFromSchedule() = 0;
+};
+
+} // namespace internal
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_EXECUTOR_H_
diff --git a/Firestore/core/src/firebase/firestore/util/executor_libdispatch.h b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.h
new file mode 100644
index 0000000..f913fe5
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_EXECUTOR_LIBDISPATCH_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_EXECUTOR_LIBDISPATCH_H_
+
+#include <chrono> // NOLINT(build/c++11)
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+#include "dispatch/dispatch.h"
+
+#include "Firestore/core/src/firebase/firestore/util/executor.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "absl/strings/string_view.h"
+
+#if !defined(__OBJC__)
+// `dispatch_queue_t` gets defined to different types when compiled in C++ or
+// Objective-C mode. Source files including this header should all be compiled
+// in the same mode to avoid linker errors.
+#error "This header only supports Objective-C++ (see comment for more info)."
+#endif // !defined(__OBJC__)
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+namespace internal {
+
+// Generic wrapper over `dispatch_async_f`, providing `dispatch_async`-like
+// interface: accepts an arbitrary invocable object in place of an Objective-C
+// block.
+void DispatchAsync(dispatch_queue_t queue, std::function<void()>&& work);
+
+// Similar to `DispatchAsync` but wraps `dispatch_sync_f`.
+void DispatchSync(dispatch_queue_t queue, std::function<void()> work);
+
+class TimeSlot;
+
+// A serial queue built on top of libdispatch. The operations are run on
+// a dedicated serial dispatch queue.
+class ExecutorLibdispatch : public Executor {
+ public:
+ explicit ExecutorLibdispatch(dispatch_queue_t dispatch_queue);
+
+ bool IsCurrentExecutor() const override;
+ std::string CurrentExecutorName() const override;
+ std::string Name() const override;
+
+ void Execute(Operation&& operation) override;
+ void ExecuteBlocking(Operation&& operation) override;
+ DelayedOperation Schedule(Milliseconds delay,
+ TaggedOperation&& operation) override;
+
+ void RemoveFromSchedule(const TimeSlot* to_remove);
+
+ bool IsScheduled(Tag tag) const override;
+ absl::optional<TaggedOperation> PopFromSchedule() override;
+
+ dispatch_queue_t dispatch_queue() const {
+ return dispatch_queue_;
+ }
+
+ private:
+ dispatch_queue_t dispatch_queue_;
+ // Stores non-owned pointers to `TimeSlot`s.
+ // Invariant: if a `TimeSlot` is in `schedule_`, it's a valid pointer.
+ std::vector<TimeSlot*> schedule_;
+};
+
+} // namespace internal
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_EXECUTOR_LIBDISPATCH_H_
diff --git a/Firestore/core/src/firebase/firestore/util/executor_libdispatch.mm b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.mm
new file mode 100644
index 0000000..597d450
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.mm
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+namespace internal {
+
+namespace {
+
+absl::string_view StringViewFromDispatchLabel(const char* const label) {
+ // Make sure string_view's data is not null, because it's used for logging.
+ return label ? absl::string_view{label} : absl::string_view{""};
+}
+
+// GetLabel functions are guaranteed to never return a "null" string_view
+// (i.e. data() != nullptr).
+absl::string_view GetQueueLabel(const dispatch_queue_t queue) {
+ return StringViewFromDispatchLabel(dispatch_queue_get_label(queue));
+}
+absl::string_view GetCurrentQueueLabel() {
+ // Note: dispatch_queue_get_label may return nullptr if the queue wasn't
+ // initialized with a label.
+ return StringViewFromDispatchLabel(
+ dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
+}
+
+} // namespace
+
+void DispatchAsync(const dispatch_queue_t queue, std::function<void()>&& work) {
+ // Dynamically allocate the function to make sure the object is valid by the
+ // time libdispatch gets to it.
+ const auto wrap = new std::function<void()>{std::move(work)};
+
+ dispatch_async_f(queue, wrap, [](void* const raw_work) {
+ const auto unwrap = static_cast<std::function<void()>*>(raw_work);
+ (*unwrap)();
+ delete unwrap;
+ });
+}
+
+void DispatchSync(const dispatch_queue_t queue, std::function<void()> work) {
+ FIREBASE_ASSERT_MESSAGE(
+ GetCurrentQueueLabel() != GetQueueLabel(queue),
+ "Calling DispatchSync on the current queue will lead to a deadlock.");
+
+ // Unlike dispatch_async_f, dispatch_sync_f blocks until the work passed to it
+ // is done, so passing a reference to a local variable is okay.
+ dispatch_sync_f(queue, &work, [](void* const raw_work) {
+ const auto unwrap = static_cast<std::function<void()>*>(raw_work);
+ (*unwrap)();
+ });
+}
+
+namespace {
+
+template <typename Work>
+void RunSynchronized(const ExecutorLibdispatch* const executor, Work&& work) {
+ if (executor->IsCurrentExecutor()) {
+ work();
+ } else {
+ DispatchSync(executor->dispatch_queue(), std::forward<Work>(work));
+ }
+}
+
+} // namespace
+
+// Represents a "busy" time slot on the schedule.
+//
+// Since libdispatch doesn't provide a way to cancel a scheduled operation, once
+// a slot is created, it will always stay in the schedule until the time is
+// past. Consequently, it is more useful to think of a time slot than
+// a particular scheduled operation -- by the time the slot comes, operation may
+// or may not be there (imagine getting to a meeting and finding out it's been
+// canceled).
+//
+// Precondition: all member functions, including the constructor, are *only*
+// invoked on the Firestore queue.
+//
+// Ownership:
+//
+// - `TimeSlot` is exclusively owned by libdispatch;
+// - `ExecutorLibdispatch` contains non-owning pointers to `TimeSlot`s;
+// - invariant: if the executor contains a pointer to a `TimeSlot`, it is
+// a valid object. It is achieved because when libdispatch invokes
+// a `TimeSlot`, it always removes it from the executor before deleting it.
+// The reverse is not true: a canceled time slot is removed from the executor,
+// but won't be destroyed until its original due time is past.
+
+class TimeSlot {
+ public:
+ TimeSlot(ExecutorLibdispatch* executor,
+ Executor::Milliseconds delay,
+ Executor::TaggedOperation&& operation);
+
+ // Returns the operation that was scheduled for this time slot and turns the
+ // slot into a no-op.
+ Executor::TaggedOperation Unschedule();
+
+ bool operator<(const TimeSlot& rhs) const {
+ return target_time_ < rhs.target_time_;
+ }
+ bool operator==(const Executor::Tag tag) const {
+ return tagged_.tag == tag;
+ }
+
+ void MarkDone() {
+ done_ = true;
+ }
+
+ static void InvokedByLibdispatch(void* const raw_self);
+
+ private:
+ void Execute();
+ void RemoveFromSchedule();
+
+ using TimePoint = std::chrono::time_point<std::chrono::steady_clock,
+ Executor::Milliseconds>;
+
+ ExecutorLibdispatch* const executor_;
+ const TimePoint target_time_; // Used for sorting
+ Executor::TaggedOperation tagged_;
+
+ // True if the operation has either been run or canceled.
+ //
+ // Note on thread-safety: because the precondition is that all member
+ // functions of this class are executed on the dispatch queue, no
+ // synchronization is required for `done_`.
+ bool done_ = false;
+};
+
+TimeSlot::TimeSlot(ExecutorLibdispatch* const executor,
+ const Executor::Milliseconds delay,
+ Executor::TaggedOperation&& operation)
+ : executor_{executor},
+ target_time_{std::chrono::time_point_cast<Executor::Milliseconds>(
+ std::chrono::steady_clock::now()) +
+ delay},
+ tagged_{std::move(operation)} {
+}
+
+Executor::TaggedOperation TimeSlot::Unschedule() {
+ if (!done_) {
+ RemoveFromSchedule();
+ }
+ return std::move(tagged_);
+}
+
+void TimeSlot::InvokedByLibdispatch(void* const raw_self) {
+ auto const self = static_cast<TimeSlot*>(raw_self);
+ self->Execute();
+ delete self;
+}
+
+void TimeSlot::Execute() {
+ if (done_) {
+ // `done_` might mean that the executor is already destroyed, so don't call
+ // `RemoveFromSchedule`.
+ return;
+ }
+
+ RemoveFromSchedule();
+
+ FIREBASE_ASSERT_MESSAGE(tagged_.operation,
+ "TimeSlot contains an invalid function object");
+ tagged_.operation();
+}
+
+void TimeSlot::RemoveFromSchedule() {
+ executor_->RemoveFromSchedule(this);
+}
+
+// ExecutorLibdispatch
+
+ExecutorLibdispatch::ExecutorLibdispatch(const dispatch_queue_t dispatch_queue)
+ : dispatch_queue_{dispatch_queue} {
+}
+
+bool ExecutorLibdispatch::IsCurrentExecutor() const {
+ return GetCurrentQueueLabel() == GetQueueLabel(dispatch_queue());
+}
+std::string ExecutorLibdispatch::CurrentExecutorName() const {
+ return GetCurrentQueueLabel().data();
+}
+std::string ExecutorLibdispatch::Name() const {
+ return GetQueueLabel(dispatch_queue()).data();
+}
+
+void ExecutorLibdispatch::Execute(Operation&& operation) {
+ DispatchAsync(dispatch_queue(), std::move(operation));
+}
+void ExecutorLibdispatch::ExecuteBlocking(Operation&& operation) {
+ DispatchSync(dispatch_queue(), std::move(operation));
+}
+
+DelayedOperation ExecutorLibdispatch::Schedule(const Milliseconds delay,
+ TaggedOperation&& operation) {
+ namespace chr = std::chrono;
+ const dispatch_time_t delay_ns = dispatch_time(
+ DISPATCH_TIME_NOW, chr::duration_cast<chr::nanoseconds>(delay).count());
+
+ // Ownership is fully transferred to libdispatch -- because it's impossible
+ // to truly cancel work after it's been dispatched, libdispatch is
+ // guaranteed to outlive the executor, and it's possible for work to be
+ // invoked by libdispatch after the executor is destroyed. Executor only
+ // stores an observer pointer to the operation.
+
+ auto const time_slot = new TimeSlot{this, delay, std::move(operation)};
+ dispatch_after_f(delay_ns, dispatch_queue(), time_slot,
+ TimeSlot::InvokedByLibdispatch);
+ RunSynchronized(this, [this, time_slot] { schedule_.push_back(time_slot); });
+ return DelayedOperation{[this, time_slot] {
+ // `time_slot` might be destroyed by the time cancellation function runs.
+ // Therefore, don't access any methods on `time_slot`, only use it as
+ // a handle to remove from `schedule_`.
+ RemoveFromSchedule(time_slot);
+ }};
+}
+
+void ExecutorLibdispatch::RemoveFromSchedule(const TimeSlot* const to_remove) {
+ RunSynchronized(this, [this, to_remove] {
+ const auto found = std::find_if(
+ schedule_.begin(), schedule_.end(),
+ [to_remove](const TimeSlot* op) { return op == to_remove; });
+ // It's possible for the operation to be missing if libdispatch gets to run
+ // it after it was force-run, for example.
+ if (found != schedule_.end()) {
+ (*found)->MarkDone();
+ schedule_.erase(found);
+ }
+ });
+}
+
+// Test-only methods
+
+bool ExecutorLibdispatch::IsScheduled(const Tag tag) const {
+ bool result = false;
+ RunSynchronized(this, [this, tag, &result] {
+ result = std::any_of(
+ schedule_.begin(), schedule_.end(),
+ [&tag](const TimeSlot* const operation) { return *operation == tag; });
+ });
+ return result;
+}
+
+absl::optional<Executor::TaggedOperation>
+ExecutorLibdispatch::PopFromSchedule() {
+ absl::optional<Executor::TaggedOperation> result;
+
+ RunSynchronized(this, [this, &result] {
+ if (schedule_.empty()) {
+ return;
+ }
+ // Sorting upon each call to `PopFromSchedule` is inefficient, which is
+ // consciously ignored because this function is only ever called from tests.
+ std::sort(
+ schedule_.begin(), schedule_.end(),
+ [](const TimeSlot* lhs, const TimeSlot* rhs) { return *lhs < *rhs; });
+ const auto nearest = schedule_.begin();
+ result = (*nearest)->Unschedule();
+ });
+
+ return result;
+}
+
+} // namespace internal
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/util/executor_std.cc b/Firestore/core/src/firebase/firestore/util/executor_std.cc
new file mode 100644
index 0000000..f03a712
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/executor_std.cc
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/executor_std.h"
+
+#include <future> // NOLINT(build/c++11)
+#include <sstream>
+
+namespace firebase {
+namespace firestore {
+namespace util {
+namespace internal {
+
+namespace {
+
+// The only guarantee is that different `thread_id`s will produce different
+// values.
+std::string ThreadIdToString(const std::thread::id thread_id) {
+ std::ostringstream stream;
+ stream << thread_id;
+ return stream.str();
+}
+
+} // namespace
+
+ExecutorStd::ExecutorStd() {
+ // Somewhat counter-intuitively, constructor of `std::atomic` assigns the
+ // value non-atomically, so the atomic initialization must be provided here,
+ // before the worker thread is started.
+ // See [this thread](https://stackoverflow.com/questions/25609858) for context
+ // on the constructor.
+ current_id_ = 0;
+ shutting_down_ = false;
+ worker_thread_ = std::thread{&ExecutorStd::PollingThread, this};
+}
+
+ExecutorStd::~ExecutorStd() {
+ shutting_down_ = true;
+ // Make sure the worker thread is not blocked, so that the call to `join`
+ // doesn't hang.
+ UnblockQueue();
+ worker_thread_.join();
+}
+
+void ExecutorStd::Execute(Operation&& operation) {
+ PushOnSchedule(std::move(operation), Immediate());
+}
+
+DelayedOperation ExecutorStd::Schedule(const Milliseconds delay,
+ TaggedOperation&& tagged) {
+ // While negative delay can be interpreted as a request for immediate
+ // execution, supporting it would provide a hacky way to modify FIFO ordering
+ // of immediate operations.
+ FIREBASE_ASSERT_MESSAGE(delay.count() >= 0,
+ "Schedule: delay cannot be negative");
+
+ namespace chr = std::chrono;
+ const auto now = chr::time_point_cast<Milliseconds>(chr::steady_clock::now());
+ const auto id =
+ PushOnSchedule(std::move(tagged.operation), now + delay, tagged.tag);
+
+ return DelayedOperation{[this, id] { TryCancel(id); }};
+}
+
+void ExecutorStd::TryCancel(const Id operation_id) {
+ schedule_.RemoveIf(
+ [operation_id](const Entry& e) { return e.id == operation_id; });
+}
+
+ExecutorStd::Id ExecutorStd::PushOnSchedule(Operation&& operation,
+ const TimePoint when,
+ const Tag tag) {
+ // Note: operations scheduled for immediate execution don't actually need an
+ // id. This could be tweaked to reuse the same id for all such operations.
+ const auto id = NextId();
+ schedule_.Push(Entry{std::move(operation), id, tag}, when);
+ return id;
+}
+
+void ExecutorStd::PollingThread() {
+ while (!shutting_down_) {
+ Entry entry = schedule_.PopBlocking();
+ if (entry.tagged.operation) {
+ entry.tagged.operation();
+ }
+ }
+}
+
+void ExecutorStd::UnblockQueue() {
+ // Put a no-op for immediate execution on the queue to ensure that
+ // `schedule_.PopBlocking` returns, and worker thread can notice that shutdown
+ // is in progress.
+ schedule_.Push(Entry{[] {}, /*id=*/0}, Immediate());
+}
+
+ExecutorStd::Id ExecutorStd::NextId() {
+ // The wrap around after ~4 billion operations is explicitly ignored. Even if
+ // an instance of `ExecutorStd` runs long enough to get `current_id_` to
+ // overflow, it's extremely unlikely that any object still holds a reference
+ // that is old enough to cause a conflict.
+ return current_id_++;
+}
+
+bool ExecutorStd::IsCurrentExecutor() const {
+ return std::this_thread::get_id() == worker_thread_.get_id();
+}
+
+std::string ExecutorStd::CurrentExecutorName() const {
+ return ThreadIdToString(std::this_thread::get_id());
+}
+
+std::string ExecutorStd::Name() const {
+ return ThreadIdToString(worker_thread_.get_id());
+}
+
+void ExecutorStd::ExecuteBlocking(Operation&& operation) {
+ std::promise<void> signal_finished;
+ Execute([&] {
+ operation();
+ signal_finished.set_value();
+ });
+ signal_finished.get_future().wait();
+}
+
+bool ExecutorStd::IsScheduled(const Tag tag) const {
+ return schedule_.Contains(
+ [&tag](const Entry& e) { return e.tagged.tag == tag; });
+}
+
+absl::optional<Executor::TaggedOperation> ExecutorStd::PopFromSchedule() {
+ auto removed =
+ schedule_.RemoveIf([](const Entry& e) { return !e.IsImmediate(); });
+ if (!removed.has_value()) {
+ return {};
+ }
+ return {std::move(removed.value().tagged)};
+}
+
+} // namespace internal
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/src/firebase/firestore/util/executor_std.h b/Firestore/core/src/firebase/firestore/util/executor_std.h
new file mode 100644
index 0000000..8f2be02
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/executor_std.h
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_EXECUTOR_STD_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_EXECUTOR_STD_H_
+
+#include <algorithm>
+#include <atomic>
+#include <condition_variable> // NOLINT(build/c++11)
+#include <deque>
+#include <mutex> // NOLINT(build/c++11)
+#include <string>
+#include <thread> // NOLINT(build/c++11)
+#include <utility>
+
+#include "Firestore/core/src/firebase/firestore/util/executor.h"
+#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
+#include "absl/types/optional.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+namespace async {
+
+// A thread-safe class similar to a priority queue where the entries are
+// prioritized by the time for which they're scheduled. Entries scheduled for
+// the exact same time are prioritized in FIFO order.
+//
+// The main function of `Schedule` is `PopBlocking`, which sleeps until an entry
+// becomes available. It correctly handles entries being asynchonously added or
+// removed from the schedule.
+//
+// The details of time management are completely concealed within the class.
+// Once an entry is scheduled, there is no way to reschedule or even retrieve
+// the time.
+template <typename T>
+class Schedule {
+ // Internal invariants:
+ // - entries are always in sorted order, leftmost entry is always the most
+ // due;
+ // - each operation modifying the queue notifies the condition variable `cv_`.
+ public:
+ using Duration = std::chrono::milliseconds;
+ using Clock = std::chrono::steady_clock;
+ // Entries are scheduled using absolute time.
+ using TimePoint = std::chrono::time_point<Clock, Duration>;
+
+ // Schedules an entry for the specified time due. `due` may be in the past.
+ void Push(const T& value, const TimePoint due) {
+ InsertPreservingOrder(Entry{value, due});
+ }
+ void Push(T&& value, const TimePoint due) {
+ InsertPreservingOrder(Entry{std::move(value), due});
+ }
+
+ // If the queue contains at least one entry for which the scheduled time is
+ // due now (according to the system clock), removes the entry which is the
+ // most overdue from the queue and returns it. If no entry is due, returns an
+ // empty `optional`.
+ absl::optional<T> PopIfDue() {
+ std::lock_guard<std::mutex> lock{mutex_};
+
+ if (HasDueLocked()) {
+ return ExtractLocked(scheduled_.begin());
+ }
+ return {};
+ }
+
+ // Blocks until at least one entry is available for which the scheduled time
+ // is due now (according to the system clock), removes the entry which is the
+ // most overdue from the queue and returns it. The function will
+ // attempt to minimize both the waiting time and busy waiting.
+ T PopBlocking() {
+ std::unique_lock<std::mutex> lock{mutex_};
+
+ while (true) {
+ cv_.wait(lock, [this] { return !scheduled_.empty(); });
+
+ // To minimize busy waiting, sleep until either the nearest entry in the
+ // future either changes, or else becomes due.
+ const auto until = scheduled_.front().due;
+ cv_.wait_until(lock, until,
+ [this, until] { return scheduled_.front().due != until; });
+ // There are 3 possibilities why `wait_until` has returned:
+ // - `wait_until` has timed out, in which case the current time is at
+ // least `until`, so there must be an overdue entry;
+ // - a new entry has been added which comes before `until`. It must be
+ // either overdue (in which case `HasDueLocked` will break the cycle),
+ // or else `until` must be reevaluated (on the next iteration of the
+ // loop);
+ // - `until` entry has been removed. This means `until` has to be
+ // reevaluated, similar to #2.
+
+ if (HasDueLocked()) {
+ return ExtractLocked(scheduled_.begin());
+ }
+ }
+ }
+
+ bool empty() const {
+ std::lock_guard<std::mutex> lock{mutex_};
+ return scheduled_.empty();
+ }
+
+ size_t size() const {
+ std::lock_guard<std::mutex> lock{mutex_};
+ return scheduled_.size();
+ }
+
+ // Removes the first entry satisfying predicate from the queue and returns it.
+ // If no such entry exists, returns an empty `optional`. Predicate is applied
+ // to entries in order according to their scheduled time.
+ //
+ // Note that this function doesn't take into account whether the removed entry
+ // is past its due time.
+ template <typename Pred>
+ absl::optional<T> RemoveIf(const Pred pred) {
+ std::lock_guard<std::mutex> lock{mutex_};
+
+ for (auto iter = scheduled_.begin(), end = scheduled_.end(); iter != end;
+ ++iter) {
+ if (pred(iter->value)) {
+ return ExtractLocked(iter);
+ }
+ }
+ return {};
+ }
+
+ // Checks whether the queue contains an entry satisfying the given predicate.
+ template <typename Pred>
+ bool Contains(const Pred pred) const {
+ std::lock_guard<std::mutex> lock{mutex_};
+ return std::any_of(scheduled_.begin(), scheduled_.end(),
+ [&pred](const Entry& s) { return pred(s.value); });
+ }
+
+ private:
+ struct Entry {
+ bool operator<(const Entry& rhs) const {
+ return due < rhs.due;
+ }
+
+ T value;
+ TimePoint due;
+ };
+ // All removals are on the front, but most insertions are expected to be on
+ // the back.
+ using Container = std::deque<Entry>;
+ using Iterator = typename Container::iterator;
+
+ void InsertPreservingOrder(Entry&& new_entry) {
+ std::lock_guard<std::mutex> lock{mutex_};
+
+ const auto insertion_point =
+ std::upper_bound(scheduled_.begin(), scheduled_.end(), new_entry);
+ scheduled_.insert(insertion_point, std::move(new_entry));
+
+ cv_.notify_one();
+ }
+
+ // This function expects the mutex to be already locked.
+ bool HasDueLocked() const {
+ namespace chr = std::chrono;
+ const auto now = chr::time_point_cast<Duration>(Clock::now());
+ return !scheduled_.empty() && now >= scheduled_.front().due;
+ }
+
+ // This function expects the mutex to be already locked.
+ T ExtractLocked(const Iterator where) {
+ FIREBASE_ASSERT_MESSAGE(!scheduled_.empty(),
+ "Trying to pop an entry from an empty queue.");
+
+ T result = std::move(where->value);
+ scheduled_.erase(where);
+ cv_.notify_one();
+
+ return result;
+ }
+
+ mutable std::mutex mutex_;
+ std::condition_variable cv_;
+ Container scheduled_;
+};
+
+} // namespace async
+
+namespace internal {
+
+// A serial queue that executes provided operations on a dedicated background
+// thread, using C++11 standard library functionality.
+class ExecutorStd : public Executor {
+ public:
+ ExecutorStd();
+ ~ExecutorStd();
+
+ void Execute(Operation&& operation) override;
+ void ExecuteBlocking(Operation&& operation) override;
+
+ DelayedOperation Schedule(Milliseconds delay,
+ TaggedOperation&& tagged) override;
+
+ bool IsCurrentExecutor() const override;
+ std::string CurrentExecutorName() const override;
+ std::string Name() const override;
+
+ bool IsScheduled(Tag tag) const override;
+ absl::optional<TaggedOperation> PopFromSchedule() override;
+
+ using TimePoint = async::Schedule<Operation>::TimePoint;
+ // To allow canceling operations, each scheduled operation is assigned
+ // a monotonically increasing identifier.
+ using Id = unsigned int;
+
+ // If the operation hasn't yet been run, it will be removed from the queue.
+ // Otherwise, this function is a no-op.
+ void TryCancel(Id operation_id);
+
+ Id PushOnSchedule(Operation&& operation, TimePoint when, Tag tag = -1);
+
+ void PollingThread();
+ void UnblockQueue();
+ Id NextId();
+
+ // As a convention, assign the epoch time to all operations scheduled for
+ // immediate execution. Note that it means that an immediate operation is
+ // always scheduled before any delayed operation, even in the corner case when
+ // the immediate operation was scheduled after a delayed operation was due
+ // (but hasn't yet run).
+ static TimePoint Immediate() {
+ return TimePoint{};
+ }
+
+ struct Entry {
+ Entry() {
+ }
+ Entry(Operation&& operation,
+ const ExecutorStd::Id id,
+ const ExecutorStd::Tag tag = kNoTag)
+ : tagged{tag, std::move(operation)}, id{id} {
+ }
+
+ bool IsImmediate() const {
+ return tagged.tag == kNoTag;
+ }
+
+ static constexpr Tag kNoTag = -1;
+ TaggedOperation tagged;
+ Id id = 0;
+ };
+ // Operations scheduled for immediate execution are also put on the schedule
+ // (with due time set to `Immediate`).
+ async::Schedule<Entry> schedule_;
+
+ std::thread worker_thread_;
+ // Used to stop the worker thread.
+ std::atomic<bool> shutting_down_{false};
+
+ std::atomic<Id> current_id_{0};
+};
+
+} // namespace internal
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_EXECUTOR_STD_H_
diff --git a/Firestore/core/src/firebase/firestore/util/hashing.h b/Firestore/core/src/firebase/firestore/util/hashing.h
new file mode 100644
index 0000000..d8058c8
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/hashing.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_HASHING_H_
+#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_HASHING_H_
+
+#include <iterator>
+#include <type_traits>
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+// This is a pretty terrible hash implementation for lack of a better one being
+// readily available. It exists as a portability crutch between our existing
+// Objective-C code where overriding `-isEqual:` also requires `-hash` and C++
+// where `operator==()` can be defined without defining a hash code.
+//
+// It's based on the recommendation in Effective Java, Item 9, wherein you
+// implement composite hashes like so:
+//
+// size_t result = first_;
+// result = 31 * result + second_;
+// result = 31 * result + third_;
+// // ...
+// return result;
+//
+// This is the basis of this implementation because that's what the existing
+// Objective-C code mostly does by hand. Using this implementation gets the
+// same result by calling
+//
+// return util::Hash(first_, second_, /* ..., */ third_);
+//
+// TODO(wilhuff): Replace this with whatever Abseil releases.
+
+namespace impl {
+
+/**
+ * Combines a hash_value with whatever accumulated state there is so far.
+ */
+inline size_t Combine(size_t state, size_t hash_value) {
+ return 31 * state + hash_value;
+}
+
+/**
+ * Explicit ordering of hashers, allowing SFINAE without all the enable_if
+ * cruft.
+ *
+ * In order we try:
+ * * A Hash() member, if defined and the return type is an integral type
+ * * A std::hash specialization, if available
+ * * A range-based specialization, valid if either of the above hold on the
+ * members of the range.
+ *
+ * Explicit ordering resolves the ambiguity of the case where a std::hash
+ * specialization is available, but the type is also a range for whose members
+ * std::hash is also available, e.g. with std::string.
+ *
+ * HashChoice is a recursive type, defined such that HashChoice<0> is the most
+ * specific type with HashChoice<1> and beyond being progressively less
+ * specific. This causes the compiler to prioritize the overloads with
+ * lower-numbered HashChoice types, allowing compilation to succeed even if
+ * multiple specializations match.
+ */
+template <int I>
+struct HashChoice : HashChoice<I + 1> {};
+
+template <>
+struct HashChoice<2> {};
+
+template <typename K>
+size_t InvokeHash(const K& value);
+
+/**
+ * Hashes the given value if it defines a Hash() member.
+ *
+ * @return The result of `value.Hash()`.
+ */
+template <typename K>
+auto RankedInvokeHash(const K& value, HashChoice<0>) -> decltype(value.Hash()) {
+ return value.Hash();
+}
+
+/**
+ * Hashes the given value if it has a specialization of std::hash.
+ *
+ * @return The result of `std::hash<K>{}(value)`
+ */
+template <typename K>
+auto RankedInvokeHash(const K& value, HashChoice<1>)
+ -> decltype(std::hash<K>{}(value)) {
+ return std::hash<K>{}(value);
+}
+
+/**
+ * Hashes the contents of the given range of values if the value_type of the
+ * range can be hashed.
+ */
+template <typename Range>
+auto RankedInvokeHash(const Range& range, HashChoice<2>)
+ -> decltype(impl::InvokeHash(*std::begin(range))) {
+ size_t result = 0;
+ size_t size = 0;
+ for (auto&& element : range) {
+ ++size;
+ result = Combine(result, InvokeHash(element));
+ }
+ result = Combine(result, size);
+ return result;
+}
+
+template <typename K>
+size_t InvokeHash(const K& value) {
+ return RankedInvokeHash(value, HashChoice<0>{});
+}
+
+inline size_t HashInternal(size_t state) {
+ return state;
+}
+
+template <typename T, typename... Ts>
+size_t HashInternal(size_t state, const T& value, const Ts&... rest) {
+ state = Combine(state, InvokeHash(value));
+ return HashInternal(state, rest...);
+}
+
+} // namespace impl
+
+template <typename... Ts>
+size_t Hash(const Ts&... values) {
+ return impl::HashInternal(0u, values...);
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_HASHING_H_
diff --git a/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt b/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt
index 753e2d0..aa8643b 100644
--- a/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt
+++ b/Firestore/core/test/firebase/firestore/immutable/CMakeLists.txt
@@ -18,6 +18,7 @@ cc_test(
array_sorted_map_test.cc
testing.h
sorted_map_test.cc
+ sorted_set_test.cc
tree_sorted_map_test.cc
DEPENDS
firebase_firestore_immutable
diff --git a/Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc b/Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc
index bcacb50..75353d9 100644
--- a/Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc
+++ b/Firestore/core/test/firebase/firestore/immutable/sorted_map_test.cc
@@ -18,6 +18,7 @@
#include <numeric>
#include <random>
+#include <type_traits>
#include <unordered_set>
#include <utility>
@@ -297,6 +298,9 @@ TYPED_TEST(SortedMapTest, MinMax) {
}
TYPED_TEST(SortedMapTest, IteratorsAreDefaultConstructible) {
+ ASSERT_TRUE(
+ std::is_default_constructible<typename TypeParam::const_iterator>::value);
+
// If this compiles the test has succeeded
typename TypeParam::const_iterator iter;
(void)iter;
diff --git a/Firestore/core/test/firebase/firestore/immutable/sorted_set_test.cc b/Firestore/core/test/firebase/firestore/immutable/sorted_set_test.cc
new file mode 100644
index 0000000..a4b337c
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/immutable/sorted_set_test.cc
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/src/firebase/firestore/immutable/sorted_set.h"
+
+#include <random>
+#include <unordered_set>
+
+#include "Firestore/core/test/firebase/firestore/immutable/testing.h"
+
+using firebase::firestore::immutable::impl::SortedMapBase;
+using SizeType = SortedMapBase::size_type;
+
+namespace firebase {
+namespace firestore {
+namespace immutable {
+
+template <typename K>
+SortedSet<K> ToSet(const std::vector<K>& container) {
+ SortedSet<K> result;
+ for (auto&& entry : container) {
+ result = result.insert(entry);
+ }
+ return result;
+}
+
+static const int kLargeNumber = 100;
+
+TEST(SortedSetTest, EmptyBehavior) {
+ SortedSet<int> set;
+
+ EXPECT_TRUE(set.empty());
+ EXPECT_EQ(0u, set.size());
+
+ EXPECT_TRUE(NotFound(set, 1));
+}
+
+TEST(SortedSetTest, Size) {
+ std::mt19937 rand;
+ std::uniform_int_distribution<int> dist(0, 999);
+
+ std::unordered_set<int> expected;
+
+ SortedSet<int> set;
+ for (int i = 0; i < kLargeNumber; ++i) {
+ int value = dist(rand);
+
+ // The random number sequence can generate duplicates, so the expected size
+ // won't necessarily depend upon `i`.
+ expected.insert(value);
+
+ set = set.insert(value);
+ EXPECT_EQ(expected.size(), set.size());
+ }
+
+ for (int i = 0; i < kLargeNumber; ++i) {
+ int value = dist(rand);
+
+ // The random number sequence can generate duplicates, so the expected size
+ // won't necessarily depend upon `i`.
+ expected.erase(value);
+
+ set = set.erase(value);
+ EXPECT_EQ(expected.size(), set.size());
+ }
+}
+
+TEST(SortedSetSet, Find) {
+ SortedSet<int> set = SortedSet<int>{}.insert(1).insert(2).insert(4);
+
+ EXPECT_TRUE(NotFound(set, 0));
+ EXPECT_TRUE(Found(set, 1));
+ EXPECT_TRUE(Found(set, 2));
+ EXPECT_TRUE(NotFound(set, 3));
+ EXPECT_TRUE(Found(set, 4));
+ EXPECT_TRUE(NotFound(set, 5));
+}
+
+TEST(SortedSetTest, IteratorsAreDefaultConstructible) {
+ static_assert(
+ std::is_default_constructible<SortedSet<int>::const_iterator>::value,
+ "is default constructible");
+}
+
+TEST(SortedSetTest, CanBeConstructedFromSortedMap) {
+ using Map = SortedMap<int, int>;
+
+ Map map = Map{}.insert(1, 2).insert(3, 4);
+ auto set = MakeSortedSet(map);
+
+ ASSERT_TRUE(Found(set, 1));
+ ASSERT_TRUE(NotFound(set, 2));
+
+ // Set insertion does not modify the underlying map
+ set = set.insert(2);
+ ASSERT_TRUE(Found(set, 2));
+ ASSERT_TRUE(NotFound(map, 2));
+}
+
+TEST(SortedSetTest, Iterator) {
+ std::vector<int> all = Sequence(kLargeNumber);
+ SortedSet<int> set = ToSet(Shuffled(all));
+
+ auto begin = set.begin();
+ ASSERT_EQ(0, *begin);
+
+ auto end = set.end();
+ ASSERT_EQ(all.size(), static_cast<size_t>(std::distance(begin, end)));
+
+ ASSERT_SEQ_EQ(all, set);
+}
+
+TEST(SortedSetTest, ValuesFrom) {
+ std::vector<int> all = Sequence(2, 42, 2);
+ SortedSet<int> set = ToSet(Shuffled(all));
+ ASSERT_EQ(20u, set.size());
+
+ // Test from before keys.
+ ASSERT_SEQ_EQ(all, set.values_from(0));
+
+ // Test from after keys.
+ ASSERT_SEQ_EQ(Empty(), set.values_from(100));
+
+ // Test from a key in the set: should start at that key.
+ ASSERT_SEQ_EQ(Sequence(10, 42, 2), set.values_from(10));
+
+ // Test from in between keys: should start just after that key.
+ ASSERT_SEQ_EQ(Sequence(12, 42, 2), set.values_from(11));
+}
+
+TEST(SortedSetTest, ValuesIn) {
+ std::vector<int> all = Sequence(2, 42, 2);
+ SortedSet<int> set = ToSet(Shuffled(all));
+ ASSERT_EQ(20u, set.size());
+
+ // Constructs a sequence from `start` up to but not including `end` by 2.
+ auto Seq = [](int start, int end) { return Sequence(start, end, 2); };
+
+ ASSERT_SEQ_EQ(Empty(), set.values_in(0, 1)); // before to before
+ ASSERT_SEQ_EQ(all, set.values_in(0, 100)) // before to after
+ ASSERT_SEQ_EQ(Seq(2, 6), set.values_in(0, 6)) // before to in set
+ ASSERT_SEQ_EQ(Seq(2, 8), set.values_in(0, 7)) // before to in between
+
+ ASSERT_SEQ_EQ(Empty(), set.values_in(100, 0)); // after to before
+ ASSERT_SEQ_EQ(Empty(), set.values_in(100, 110)); // after to after
+ ASSERT_SEQ_EQ(Empty(), set.values_in(100, 6)); // after to in set
+ ASSERT_SEQ_EQ(Empty(), set.values_in(100, 7)); // after to in between
+
+ ASSERT_SEQ_EQ(Empty(), set.values_in(6, 0)); // in set to before
+ ASSERT_SEQ_EQ(Seq(6, 42), set.values_in(6, 100)); // in set to after
+ ASSERT_SEQ_EQ(Seq(6, 10), set.values_in(6, 10)); // in set to in set
+ ASSERT_SEQ_EQ(Seq(6, 12), set.values_in(6, 11)); // in set to in between
+
+ ASSERT_SEQ_EQ(Empty(), set.values_in(7, 0)); // in between to before
+ ASSERT_SEQ_EQ(Seq(8, 42), set.values_in(7, 100)); // in between to after
+ ASSERT_SEQ_EQ(Seq(8, 10), set.values_in(7, 10)); // in between to key in set
+ ASSERT_SEQ_EQ(Seq(8, 14), set.values_in(7, 13)); // in between to in between
+}
+
+TEST(SortedSetTest, HashesStdHashable) {
+ SortedSet<int> set;
+
+ size_t result = util::Hash(set);
+ (void)result;
+}
+
+} // namespace immutable
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/immutable/testing.h b/Firestore/core/test/firebase/firestore/immutable/testing.h
index 9e839c6..8e496dd 100644
--- a/Firestore/core/test/firebase/firestore/immutable/testing.h
+++ b/Firestore/core/test/firebase/firestore/immutable/testing.h
@@ -18,16 +18,33 @@
#define FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_IMMUTABLE_TESTING_H_
#include <algorithm>
+#include <string>
+#include <type_traits>
#include <utility>
#include <vector>
#include "Firestore/core/src/firebase/firestore/util/secure_random.h"
+#include "absl/strings/str_cat.h"
#include "gtest/gtest.h"
namespace firebase {
namespace firestore {
namespace immutable {
+template <typename K, typename V>
+std::string Describe(const std::pair<K, V>& pair) {
+ return absl::StrCat("(", pair.first, ", ", pair.second, ")");
+}
+
+// Describes the given item by its std::to_string implementation (if
+// std::to_string is defined for V). The return type is not defined directly
+// in terms of std::string in order to allow specialization failure to select
+// a different overload.
+template <typename V>
+auto Describe(const V& item) -> decltype(std::to_string(item)) {
+ return std::to_string(item);
+}
+
template <typename Container, typename K>
testing::AssertionResult NotFound(const Container& map, const K& key) {
if (map.contains(key)) {
@@ -40,11 +57,15 @@ testing::AssertionResult NotFound(const Container& map, const K& key) {
return testing::AssertionSuccess();
} else {
return testing::AssertionFailure()
- << "Should not have found (" << found->first << ", " << found->second
- << ")";
+ << "Should not have found " << Describe(*found);
}
}
+/**
+ * Asserts that the given key is found in the given container and that it maps
+ * to the given value. This only works with map-type containers where value_type
+ * is `std::pair<K, V>`.
+ */
template <typename Container, typename K, typename V>
testing::AssertionResult Found(const Container& map,
const K& key,
@@ -67,6 +88,31 @@ testing::AssertionResult Found(const Container& map,
}
}
+/**
+ * Asserts that the given key is found in the given container without
+ * necessarily checking that the key maps to any value. This also makes
+ * this compatible with non-mapped containers where K is the value_type.
+ */
+template <typename Container, typename K>
+testing::AssertionResult Found(const Container& container, const K& key) {
+ if (!container.contains(key)) {
+ return testing::AssertionFailure()
+ << "Did not find key " << key << " using contains()";
+ }
+
+ auto found = container.find(key);
+ if (found == container.end()) {
+ return testing::AssertionFailure()
+ << "Did not find key " << key << " using find()";
+ }
+ if (*found == key) {
+ return testing::AssertionSuccess();
+ } else {
+ return testing::AssertionFailure()
+ << "Found entry was " << Describe(*found);
+ }
+}
+
/** Creates an empty vector (for readability). */
inline std::vector<int> Empty() {
return {};
diff --git a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt
index 9c94677..b38d658 100644
--- a/Firestore/core/test/firebase/firestore/model/CMakeLists.txt
+++ b/Firestore/core/test/firebase/firestore/model/CMakeLists.txt
@@ -20,14 +20,12 @@ cc_test(
document_test.cc
field_mask_test.cc
field_path_test.cc
- field_transform_test.cc
field_value_test.cc
maybe_document_test.cc
no_document_test.cc
precondition_test.cc
resource_path_test.cc
snapshot_version_test.cc
- transform_operations_test.cc
DEPENDS
firebase_firestore_model
)
diff --git a/Firestore/core/test/firebase/firestore/model/document_key_test.cc b/Firestore/core/test/firebase/firestore/model/document_key_test.cc
index 619ee7f..71b78d1 100644
--- a/Firestore/core/test/firebase/firestore/model/document_key_test.cc
+++ b/Firestore/core/test/firebase/firestore/model/document_key_test.cc
@@ -21,8 +21,11 @@
#include "Firestore/core/src/firebase/firestore/model/document_key.h"
#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
#include "gtest/gtest.h"
+using firebase::firestore::testutil::Key;
+
namespace firebase {
namespace firestore {
namespace model {
@@ -112,16 +115,16 @@ TEST(DocumentKey, IsDocumentKey) {
}
TEST(DocumentKey, Comparison) {
- const DocumentKey abcd({"a", "b", "c", "d"});
- const DocumentKey abcd_too({"a", "b", "c", "d"});
- const DocumentKey xyzw({"x", "y", "z", "w"});
+ DocumentKey abcd = Key("a/b/c/d");
+ DocumentKey abcd_too = Key("a/b/c/d");
+ DocumentKey xyzw = Key("x/y/z/w");
EXPECT_EQ(abcd, abcd_too);
EXPECT_NE(abcd, xyzw);
- const DocumentKey empty;
- const DocumentKey a({"a", "a"});
- const DocumentKey b({"b", "b"});
- const DocumentKey ab({"a", "a", "b", "b"});
+ DocumentKey empty;
+ DocumentKey a = Key("a/a");
+ DocumentKey b = Key("b/b");
+ DocumentKey ab = Key("a/a/b/b");
EXPECT_FALSE(empty < empty);
EXPECT_TRUE(empty <= empty);
@@ -148,6 +151,12 @@ TEST(DocumentKey, Comparison) {
EXPECT_TRUE(ab >= a);
}
+TEST(DocumentKey, Comparator) {
+ DocumentKey abcd = Key("a/b/c/d");
+ DocumentKey xyzw = Key("x/y/z/w");
+ EXPECT_TRUE(util::Comparator<DocumentKey>{}(abcd, xyzw));
+}
+
} // namespace model
} // namespace firestore
} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
index 96125f7..a147309 100644
--- a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
+++ b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
@@ -33,18 +33,41 @@
#include <vector>
#include "Firestore/Protos/cpp/google/firestore/v1beta1/document.pb.h"
+#include "Firestore/Protos/cpp/google/firestore/v1beta1/firestore.pb.h"
+#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+#include "Firestore/core/src/firebase/firestore/timestamp_internal.h"
#include "Firestore/core/src/firebase/firestore/util/status.h"
+#include "Firestore/core/src/firebase/firestore/util/statusor.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
#include "google/protobuf/stubs/common.h"
#include "google/protobuf/util/message_differencer.h"
#include "gtest/gtest.h"
+using firebase::Timestamp;
+using firebase::TimestampInternal;
+using firebase::firestore::FirestoreErrorCode;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::Document;
+using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::FieldValue;
+using firebase::firestore::model::MaybeDocument;
+using firebase::firestore::model::NoDocument;
using firebase::firestore::model::ObjectValue;
+using firebase::firestore::model::SnapshotVersion;
using firebase::firestore::remote::Serializer;
+using firebase::firestore::testutil::Key;
using firebase::firestore::util::Status;
+using firebase::firestore::util::StatusOr;
using google::protobuf::util::MessageDifferencer;
+#define ASSERT_OK(status) ASSERT_TRUE(StatusOk(status))
+#define ASSERT_NOT_OK(status) ASSERT_FALSE(StatusOk(status))
+#define EXPECT_OK(status) EXPECT_TRUE(StatusOk(status))
+#define EXPECT_NOT_OK(status) EXPECT_FALSE(StatusOk(status))
+
TEST(Serializer, CanLinkToNanopb) {
// This test doesn't actually do anything interesting as far as actually using
// nanopb is concerned but that it can run at all is proof that all the
@@ -56,8 +79,11 @@ TEST(Serializer, CanLinkToNanopb) {
// Fixture for running serializer tests.
class SerializerTest : public ::testing::Test {
public:
- SerializerTest() : serializer(/*DatabaseId("p", "d")*/) {
+ SerializerTest() : serializer(kDatabaseId) {
+ msg_diff.ReportDifferencesToString(&message_differences);
}
+
+ const DatabaseId kDatabaseId{"p", "d"};
Serializer serializer;
void ExpectRoundTrip(const FieldValue& model,
@@ -74,22 +100,88 @@ class SerializerTest : public ::testing::Test {
ExpectDeserializationRoundTrip(model, proto, type);
}
+ void ExpectRoundTrip(
+ const DocumentKey& key,
+ const FieldValue& value,
+ const SnapshotVersion& update_time,
+ const google::firestore::v1beta1::BatchGetDocumentsResponse& proto) {
+ ExpectSerializationRoundTrip(key, value, update_time, proto);
+ ExpectDeserializationRoundTrip(key, value, update_time, proto);
+ }
+
+ /**
+ * Checks the status. Don't use directly; use one of the relevant macros
+ * instead. eg:
+ *
+ * Status good_status = ...;
+ * ASSERT_OK(good_status);
+ *
+ * Status bad_status = ...;
+ * EXPECT_NOT_OK(bad_status);
+ */
+ testing::AssertionResult StatusOk(const Status& status) {
+ if (!status.ok()) {
+ return testing::AssertionFailure()
+ << "Status should have been ok, but instead contained "
+ << status.ToString();
+ }
+ return testing::AssertionSuccess();
+ }
+
+ template <typename T>
+ testing::AssertionResult StatusOk(const StatusOr<T>& status) {
+ return StatusOk(status.status());
+ }
+
+ /**
+ * Ensures that decoding fails with the given status.
+ *
+ * @param status the expected (failed) status. Only the code() is verified.
+ */
+ void ExpectFailedStatusDuringDecode(Status status,
+ const std::vector<uint8_t>& bytes) {
+ StatusOr<FieldValue> bad_status = serializer.DecodeFieldValue(bytes);
+ ASSERT_NOT_OK(bad_status);
+ EXPECT_EQ(status.code(), bad_status.status().code());
+ }
+
google::firestore::v1beta1::Value ValueProto(nullptr_t) {
- std::vector<uint8_t> bytes;
- Status status =
- serializer.EncodeFieldValue(FieldValue::NullValue(), &bytes);
- EXPECT_TRUE(status.ok());
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::NullValue());
google::firestore::v1beta1::Value proto;
bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
EXPECT_TRUE(ok);
return proto;
}
- google::firestore::v1beta1::Value ValueProto(bool b) {
+ std::vector<uint8_t> EncodeFieldValue(Serializer* serializer,
+ const FieldValue& fv) {
+ std::vector<uint8_t> bytes;
+ Status status = serializer->EncodeFieldValue(fv, &bytes);
+ EXPECT_OK(status);
+ return bytes;
+ }
+
+ std::vector<uint8_t> EncodeDocument(Serializer* serializer,
+ const DocumentKey& key,
+ const FieldValue& value) {
std::vector<uint8_t> bytes;
Status status =
- serializer.EncodeFieldValue(FieldValue::BooleanValue(b), &bytes);
- EXPECT_TRUE(status.ok());
+ serializer->EncodeDocument(key, value.object_value(), &bytes);
+ EXPECT_OK(status);
+ return bytes;
+ }
+
+ void Mutate(uint8_t* byte,
+ uint8_t expected_initial_value,
+ uint8_t new_value) {
+ ASSERT_EQ(*byte, expected_initial_value);
+ *byte = new_value;
+ }
+
+ google::firestore::v1beta1::Value ValueProto(bool b) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::BooleanValue(b));
google::firestore::v1beta1::Value proto;
bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
EXPECT_TRUE(ok);
@@ -97,10 +189,8 @@ class SerializerTest : public ::testing::Test {
}
google::firestore::v1beta1::Value ValueProto(int64_t i) {
- std::vector<uint8_t> bytes;
- Status status =
- serializer.EncodeFieldValue(FieldValue::IntegerValue(i), &bytes);
- EXPECT_TRUE(status.ok());
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::IntegerValue(i));
google::firestore::v1beta1::Value proto;
bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
EXPECT_TRUE(ok);
@@ -112,10 +202,17 @@ class SerializerTest : public ::testing::Test {
}
google::firestore::v1beta1::Value ValueProto(const std::string& s) {
- std::vector<uint8_t> bytes;
- Status status =
- serializer.EncodeFieldValue(FieldValue::StringValue(s), &bytes);
- EXPECT_TRUE(status.ok());
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::StringValue(s));
+ google::firestore::v1beta1::Value proto;
+ bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
+ EXPECT_TRUE(ok);
+ return proto;
+ }
+
+ google::firestore::v1beta1::Value ValueProto(const Timestamp& ts) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::TimestampValue(ts));
google::firestore::v1beta1::Value proto;
bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
EXPECT_TRUE(ok);
@@ -128,13 +225,11 @@ class SerializerTest : public ::testing::Test {
const google::firestore::v1beta1::Value& proto,
FieldValue::Type type) {
EXPECT_EQ(type, model.type());
- std::vector<uint8_t> bytes;
- Status status = serializer.EncodeFieldValue(model, &bytes);
- EXPECT_TRUE(status.ok());
+ std::vector<uint8_t> bytes = EncodeFieldValue(&serializer, model);
google::firestore::v1beta1::Value actual_proto;
bool ok = actual_proto.ParseFromArray(bytes.data(), bytes.size());
EXPECT_TRUE(ok);
- EXPECT_TRUE(MessageDifferencer::Equals(proto, actual_proto));
+ EXPECT_TRUE(msg_diff.Compare(proto, actual_proto)) << message_differences;
}
void ExpectDeserializationRoundTrip(
@@ -145,28 +240,87 @@ class SerializerTest : public ::testing::Test {
std::vector<uint8_t> bytes(size);
bool status = proto.SerializeToArray(bytes.data(), size);
EXPECT_TRUE(status);
- FieldValue actual_model = serializer.DecodeFieldValue(bytes);
+ StatusOr<FieldValue> actual_model_status =
+ serializer.DecodeFieldValue(bytes);
+ EXPECT_OK(actual_model_status);
+ FieldValue actual_model = actual_model_status.ValueOrDie();
EXPECT_EQ(type, actual_model.type());
EXPECT_EQ(model, actual_model);
}
-};
-// TODO(rsgowman): whoops! A previous commit performed approx s/Encodes/Writes/,
-// but should not have done so here. Change it back in this file.
+ void ExpectSerializationRoundTrip(
+ const DocumentKey& key,
+ const FieldValue& value,
+ const SnapshotVersion& update_time,
+ const google::firestore::v1beta1::BatchGetDocumentsResponse& proto) {
+ std::vector<uint8_t> bytes = EncodeDocument(&serializer, key, value);
+ google::firestore::v1beta1::Document actual_proto;
+ bool ok = actual_proto.ParseFromArray(bytes.data(), bytes.size());
+ EXPECT_TRUE(ok);
+
+ // TODO(rsgowman): Right now, we only support Document (and don't support
+ // NoDocument). That should change in the next PR or so.
+ EXPECT_TRUE(proto.has_found());
+
+ // Slight weirdness: When we *encode* a document for sending it to the
+ // backend, we don't encode the update_time (or create_time). But when we
+ // *decode* a document, we *do* decode the update_time (though we still
+ // ignore the create_time). Therefore, we'll verify the update_time
+ // independently, and then strip it out before comparing the rest.
+ EXPECT_FALSE(actual_proto.has_create_time());
+ EXPECT_EQ(update_time.timestamp().seconds(),
+ proto.found().update_time().seconds());
+ EXPECT_EQ(update_time.timestamp().nanoseconds(),
+ proto.found().update_time().nanos());
+ google::firestore::v1beta1::BatchGetDocumentsResponse proto_copy{proto};
+ proto_copy.mutable_found()->clear_update_time();
+ proto_copy.mutable_found()->clear_create_time();
+
+ EXPECT_TRUE(msg_diff.Compare(proto_copy.found(), actual_proto))
+ << message_differences;
+ }
+
+ void ExpectDeserializationRoundTrip(
+ const DocumentKey& key,
+ const FieldValue& value,
+ const SnapshotVersion& update_time,
+ const google::firestore::v1beta1::BatchGetDocumentsResponse& proto) {
+ size_t size = proto.ByteSizeLong();
+ std::vector<uint8_t> bytes(size);
+ bool status = proto.SerializeToArray(bytes.data(), size);
+ EXPECT_TRUE(status);
+ StatusOr<std::unique_ptr<MaybeDocument>> actual_model_status =
+ serializer.DecodeMaybeDocument(bytes);
+ EXPECT_OK(actual_model_status);
+ std::unique_ptr<MaybeDocument> actual_model =
+ std::move(actual_model_status).ValueOrDie();
+
+ // TODO(rsgowman): Right now, we only support Document (and don't support
+ // NoDocument). That should change in the next PR or so.
+ EXPECT_EQ(MaybeDocument::Type::Document, actual_model->type());
+ Document* actual_doc_model = static_cast<Document*>(actual_model.get());
+ EXPECT_EQ(key, actual_model->key());
+ EXPECT_EQ(value, actual_doc_model->data());
+ EXPECT_EQ(update_time, actual_model->version());
+ }
+
+ std::string message_differences;
+ MessageDifferencer msg_diff;
+};
-TEST_F(SerializerTest, WritesNull) {
+TEST_F(SerializerTest, EncodesNull) {
FieldValue model = FieldValue::NullValue();
ExpectRoundTrip(model, ValueProto(nullptr), FieldValue::Type::Null);
}
-TEST_F(SerializerTest, WritesBool) {
+TEST_F(SerializerTest, EncodesBool) {
for (bool bool_value : {true, false}) {
FieldValue model = FieldValue::BooleanValue(bool_value);
ExpectRoundTrip(model, ValueProto(bool_value), FieldValue::Type::Boolean);
}
}
-TEST_F(SerializerTest, WritesIntegers) {
+TEST_F(SerializerTest, EncodesIntegers) {
std::vector<int64_t> cases{0,
1,
-1,
@@ -181,7 +335,7 @@ TEST_F(SerializerTest, WritesIntegers) {
}
}
-TEST_F(SerializerTest, WritesString) {
+TEST_F(SerializerTest, EncodesString) {
std::vector<std::string> cases{
"",
"a",
@@ -204,7 +358,24 @@ TEST_F(SerializerTest, WritesString) {
}
}
-TEST_F(SerializerTest, WritesEmptyMap) {
+TEST_F(SerializerTest, EncodesTimestamps) {
+ std::vector<Timestamp> cases{
+ {}, // epoch
+ {1234, 0},
+ {1234, 999999999},
+ {-1234, 0},
+ {-1234, 999999999},
+ TimestampInternal::Max(),
+ TimestampInternal::Min(),
+ };
+
+ for (const Timestamp& ts_value : cases) {
+ FieldValue model = FieldValue::TimestampValue(ts_value);
+ ExpectRoundTrip(model, ValueProto(ts_value), FieldValue::Type::Timestamp);
+ }
+}
+
+TEST_F(SerializerTest, EncodesEmptyMap) {
FieldValue model = FieldValue::ObjectValueFromMap({});
google::firestore::v1beta1::Value proto;
@@ -213,7 +384,7 @@ TEST_F(SerializerTest, WritesEmptyMap) {
ExpectRoundTrip(model, proto, FieldValue::Type::Object);
}
-TEST_F(SerializerTest, WritesNestedObjects) {
+TEST_F(SerializerTest, EncodesNestedObjects) {
FieldValue model = FieldValue::ObjectValueFromMap({
{"b", FieldValue::TrueValue()},
// TODO(rsgowman): add doubles (once they're supported)
@@ -258,7 +429,206 @@ TEST_F(SerializerTest, WritesNestedObjects) {
ExpectRoundTrip(model, proto, FieldValue::Type::Object);
}
+TEST_F(SerializerTest, BadNullValue) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::NullValue());
+
+ // Alter the null value from 0 to 1.
+ Mutate(&bytes[1], /*expected_initial_value=*/0, /*new_value=*/1);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadBoolValue) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::BooleanValue(true));
+
+ // Alter the bool value from 1 to 2. (Value values are 0,1)
+ Mutate(&bytes[1], /*expected_initial_value=*/1, /*new_value=*/2);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadIntegerValue) {
+ // Encode 'maxint'. This should result in 9 0xff bytes, followed by a 1.
+ std::vector<uint8_t> bytes = EncodeFieldValue(
+ &serializer,
+ FieldValue::IntegerValue(std::numeric_limits<uint64_t>::max()));
+ ASSERT_EQ(11u, bytes.size());
+ for (size_t i = 1; i < bytes.size() - 1; i++) {
+ ASSERT_EQ(0xff, bytes[i]);
+ }
+
+ // make the number a bit bigger
+ Mutate(&bytes[10], /*expected_initial_value=*/1, /*new_value=*/0xff);
+ bytes.resize(12);
+ bytes[11] = 0x7f;
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadStringValue) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::StringValue("a"));
+
+ // Claim that the string length is 5 instead of 1. (The first two bytes are
+ // used by the encoded tag.)
+ Mutate(&bytes[2], /*expected_initial_value=*/1, /*new_value=*/5);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadTimestampValue_TooLarge) {
+ std::vector<uint8_t> bytes = EncodeFieldValue(
+ &serializer, FieldValue::TimestampValue(TimestampInternal::Max()));
+
+ // Add some time, which should push us above the maximum allowed timestamp.
+ Mutate(&bytes[4], 0x82, 0x83);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadTimestampValue_TooSmall) {
+ std::vector<uint8_t> bytes = EncodeFieldValue(
+ &serializer, FieldValue::TimestampValue(TimestampInternal::Min()));
+
+ // Remove some time, which should push us below the minimum allowed timestamp.
+ Mutate(&bytes[4], 0x92, 0x91);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadTag) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::NullValue());
+
+ // The google::firestore::v1beta1::Value value_type oneof currently has tags
+ // up to 18. For this test, we'll pick a tag that's unlikely to be added in
+ // the near term but still fits within a uint8_t even when encoded.
+ // Specifically 31. 0xf8 represents field number 31 encoded as a varint.
+ Mutate(&bytes[0], /*expected_initial_value=*/0x58, /*new_value=*/0xf8);
+
+ // TODO(rsgowman): The behaviour is *temporarily* slightly different during
+ // development; this will cause a failed assertion rather than a failed
+ // status. Remove this EXPECT_ANY_THROW statement (and reenable the
+ // following commented out statement) once the corresponding assert has been
+ // removed from serializer.cc.
+ EXPECT_ANY_THROW(ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes));
+ // ExpectFailedStatusDuringDecode(
+ // Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, TagVarintWiretypeStringMismatch) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::BooleanValue(true));
+
+ // 0x0a represents a bool value encoded as a string. (We're using a
+ // boolean_value tag here, but any tag that would be represented by a varint
+ // would do.)
+ Mutate(&bytes[0], /*expected_initial_value=*/0x08, /*new_value=*/0x0a);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, TagStringWiretypeVarintMismatch) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::StringValue("foo"));
+
+ // 0x88 represents a string value encoded as a varint.
+ Mutate(&bytes[0], /*expected_initial_value=*/0x8a, /*new_value=*/0x88);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, IncompleteFieldValue) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::NullValue());
+ ASSERT_EQ(2u, bytes.size());
+
+ // Remove the (null) payload
+ ASSERT_EQ(0x00, bytes[1]);
+ bytes.pop_back();
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, IncompleteTag) {
+ std::vector<uint8_t> bytes;
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, EncodesKey) {
+ EXPECT_EQ("projects/p/databases/d/documents", serializer.EncodeKey(Key("")));
+ EXPECT_EQ("projects/p/databases/d/documents/one/two/three/four",
+ serializer.EncodeKey(Key("one/two/three/four")));
+}
+
+TEST_F(SerializerTest, DecodesKey) {
+ EXPECT_EQ(Key(""), serializer.DecodeKey("projects/p/databases/d/documents"));
+ EXPECT_EQ(Key("one/two/three/four"),
+ serializer.DecodeKey(
+ "projects/p/databases/d/documents/one/two/three/four"));
+ // Same, but with a leading slash
+ EXPECT_EQ(Key("one/two/three/four"),
+ serializer.DecodeKey(
+ "/projects/p/databases/d/documents/one/two/three/four"));
+}
+
+TEST_F(SerializerTest, BadKey) {
+ std::vector<std::string> bad_cases{
+ "", // empty (and too short)
+ "projects/p", // too short
+ "projects/p/databases/d", // too short
+ "projects/p/databases/d/documents/odd_number_of_local_elements",
+ "projects_spelled_wrong/p/databases/d/documents",
+ "projects/p/databases_spelled_wrong/d/documents",
+ "projects/not_project_p/databases/d/documents",
+ "projects/p/databases/not_database_d/documents",
+ "projects/p/databases/d/not_documents",
+ };
+
+ for (const std::string& bad_key : bad_cases) {
+ EXPECT_ANY_THROW(serializer.DecodeKey(bad_key));
+ }
+}
+
+TEST_F(SerializerTest, EncodesEmptyDocument) {
+ DocumentKey key = DocumentKey::FromPathString("path/to/the/doc");
+ FieldValue empty_value = FieldValue::ObjectValueFromMap({});
+ SnapshotVersion update_time = SnapshotVersion{{1234, 5678}};
+
+ google::firestore::v1beta1::BatchGetDocumentsResponse proto;
+ google::firestore::v1beta1::Document* doc_proto = proto.mutable_found();
+ doc_proto->set_name(serializer.EncodeKey(key));
+ doc_proto->mutable_fields();
+
+ google::protobuf::Timestamp* update_time_proto =
+ doc_proto->mutable_update_time();
+ update_time_proto->set_seconds(1234);
+ update_time_proto->set_nanos(5678);
+
+ // Note that we ignore create time in our serializer. We never set it, and
+ // never read it (other than to throw it away). But the server could (and
+ // probably does) set it, so we need to be able to discard it properly. The
+ // ExpectRoundTrip deals with this asymmetry.
+ google::protobuf::Timestamp* create_time_proto =
+ doc_proto->mutable_create_time();
+ create_time_proto->set_seconds(8765);
+ create_time_proto->set_nanos(4321);
+
+ ExpectRoundTrip(key, empty_value, update_time, proto);
+}
+
// TODO(rsgowman): Test [en|de]coding multiple protos into the same output
// vector.
-
-// TODO(rsgowman): Death test for decoding invalid bytes.
diff --git a/Firestore/core/test/firebase/firestore/util/CMakeLists.txt b/Firestore/core/test/firebase/firestore/util/CMakeLists.txt
index e5dbec5..ea80ea2 100644
--- a/Firestore/core/test/firebase/firestore/util/CMakeLists.txt
+++ b/Firestore/core/test/firebase/firestore/util/CMakeLists.txt
@@ -61,6 +61,60 @@ if(HAVE_OPENSSL_RAND_H)
)
endif()
+## executors
+
+cc_test(
+ firebase_firestore_util_executor_std_test
+ SOURCES
+ executor_test.h
+ executor_test.cc
+ executor_std_test.cc
+ async_tests_util.h
+ DEPENDS
+ firebase_firestore_util_executor_std
+)
+
+if(HAVE_LIBDISPATCH)
+ cc_test(
+ firebase_firestore_util_executor_libdispatch_test
+ SOURCES
+ executor_test.h
+ executor_test.cc
+ executor_libdispatch_test.mm
+ async_tests_util.h
+ DEPENDS
+ firebase_firestore_util_executor_libdispatch
+ )
+endif()
+
+## async queue
+
+cc_test(
+ firebase_firestore_util_async_queue_std_test
+ SOURCES
+ async_queue_test.h
+ async_queue_test.cc
+ async_queue_std_test.cc
+ async_tests_util.h
+ DEPENDS
+ firebase_firestore_util_executor_std
+ firebase_firestore_util_async_queue
+)
+
+if(HAVE_LIBDISPATCH)
+ cc_test(
+ firebase_firestore_util_async_queue_libdispatch_test
+ SOURCES
+ async_queue_test.h
+ async_queue_test.cc
+ async_queue_libdispatch_test.mm
+ async_tests_util.h
+ DEPENDS
+ firebase_firestore_util_executor_libdispatch
+ firebase_firestore_util_async_queue
+ )
+endif()
+
## main library
cc_test(
@@ -69,6 +123,7 @@ cc_test(
autoid_test.cc
bits_test.cc
comparison_test.cc
+ hashing_test.cc
iterator_adaptors_test.cc
ordered_code_test.cc
status_test.cc
diff --git a/Firestore/core/test/firebase/firestore/util/async_queue_libdispatch_test.mm b/Firestore/core/test/firebase/firestore/util/async_queue_libdispatch_test.mm
new file mode 100644
index 0000000..f1ff394
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/async_queue_libdispatch_test.mm
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/test/firebase/firestore/util/async_queue_test.h"
+
+#include <memory>
+
+#include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h"
+#include "absl/memory/memory.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+namespace {
+
+dispatch_queue_t CreateDispatchQueue() {
+ return dispatch_queue_create("AsyncQueueTests", DISPATCH_QUEUE_SERIAL);
+}
+
+std::unique_ptr<internal::Executor> CreateExecutorFromQueue(
+ const dispatch_queue_t queue) {
+ return absl::make_unique<internal::ExecutorLibdispatch>(queue);
+}
+
+std::unique_ptr<internal::Executor> CreateExecutorLibdispatch() {
+ return CreateExecutorFromQueue(CreateDispatchQueue());
+}
+
+} // namespace
+
+INSTANTIATE_TEST_CASE_P(AsyncQueueLibdispatch,
+ AsyncQueueTest,
+ ::testing::Values(CreateExecutorLibdispatch));
+
+class AsyncQueueTestLibdispatchOnly : public TestWithTimeoutMixin,
+ public ::testing::Test {
+ public:
+ AsyncQueueTestLibdispatchOnly()
+ : underlying_queue{CreateDispatchQueue()},
+ queue{CreateExecutorFromQueue(underlying_queue)} {
+ }
+
+ dispatch_queue_t underlying_queue;
+ AsyncQueue queue;
+};
+
+// Additional tests to see how libdispatch-based version of `AsyncQueue`
+// interacts with raw usage of libdispatch.
+
+TEST_F(AsyncQueueTestLibdispatchOnly, SameQueueIsAllowedForUnownedActions) {
+ internal::DispatchAsync(underlying_queue, [this] {
+ queue.Enqueue([this] { signal_finished(); });
+ });
+ EXPECT_TRUE(WaitForTestToFinish());
+}
+
+TEST_F(AsyncQueueTestLibdispatchOnly,
+ VerifyIsCurrentQueueRequiresOperationInProgress) {
+ internal::DispatchSync(underlying_queue, [this] {
+ EXPECT_ANY_THROW(queue.VerifyIsCurrentQueue());
+ });
+}
+
+TEST_F(AsyncQueueTestLibdispatchOnly,
+ VerifyIsCurrentQueueRequiresBeingCalledOnTheQueue) {
+ ASSERT_NE(underlying_queue, dispatch_get_main_queue());
+ EXPECT_ANY_THROW(queue.VerifyIsCurrentQueue());
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/async_queue_std_test.cc b/Firestore/core/test/firebase/firestore/util/async_queue_std_test.cc
new file mode 100644
index 0000000..9e69ad0
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/async_queue_std_test.cc
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/test/firebase/firestore/util/async_queue_test.h"
+
+#include "Firestore/core/src/firebase/firestore/util/executor_std.h"
+
+#include "absl/memory/memory.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+namespace {
+
+std::unique_ptr<internal::Executor> ExecutorFactory() {
+ return absl::make_unique<internal::ExecutorStd>();
+}
+
+} // namespace
+
+INSTANTIATE_TEST_CASE_P(AsyncQueueStd,
+ AsyncQueueTest,
+ ::testing::Values(ExecutorFactory));
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/async_queue_test.cc b/Firestore/core/test/firebase/firestore/util/async_queue_test.cc
new file mode 100644
index 0000000..4247584
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/async_queue_test.cc
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/test/firebase/firestore/util/async_queue_test.h"
+
+#include <chrono> // NOLINT(build/c++11)
+#include <future> // NOLINT(build/c++11)
+#include <string>
+
+#include "Firestore/core/src/firebase/firestore/util/executor.h"
+#include "absl/memory/memory.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+namespace {
+
+// In these generic tests the specific timer ids don't matter.
+const TimerId kTimerId1 = TimerId::ListenStreamConnectionBackoff;
+const TimerId kTimerId2 = TimerId::ListenStreamIdle;
+const TimerId kTimerId3 = TimerId::WriteStreamConnectionBackoff;
+
+} // namespace
+
+TEST_P(AsyncQueueTest, Enqueue) {
+ queue.Enqueue([&] { signal_finished(); });
+ EXPECT_TRUE(WaitForTestToFinish());
+}
+
+TEST_P(AsyncQueueTest, EnqueueDisallowsNesting) {
+ queue.Enqueue([&] { // clang-format off
+ // clang-format on
+ EXPECT_ANY_THROW(queue.Enqueue([] {}));
+ signal_finished();
+ });
+
+ EXPECT_TRUE(WaitForTestToFinish());
+}
+
+TEST_P(AsyncQueueTest, EnqueueRelaxedWorksFromWithinEnqueue) {
+ queue.Enqueue([&] { // clang-format off
+ queue.EnqueueRelaxed([&] { signal_finished(); });
+ // clang-format on
+ });
+
+ EXPECT_TRUE(WaitForTestToFinish());
+}
+
+TEST_P(AsyncQueueTest, EnqueueBlocking) {
+ bool finished = false;
+ queue.EnqueueBlocking([&] { finished = true; });
+ EXPECT_TRUE(finished);
+}
+
+TEST_P(AsyncQueueTest, EnqueueBlockingDisallowsNesting) {
+ queue.EnqueueBlocking([&] { // clang-format off
+ EXPECT_ANY_THROW(queue.EnqueueBlocking([] {}););
+ // clang-format on
+ });
+}
+
+TEST_P(AsyncQueueTest, ExecuteBlockingDisallowsNesting) {
+ queue.EnqueueBlocking(
+ [&] { EXPECT_ANY_THROW(queue.ExecuteBlocking([] {});); });
+}
+
+TEST_P(AsyncQueueTest, VerifyIsCurrentQueueWorksWithOperationInProgress) {
+ queue.EnqueueBlocking([&] { EXPECT_NO_THROW(queue.VerifyIsCurrentQueue()); });
+}
+
+// TODO(varconst): this test is inherently flaky because it can't be guaranteed
+// that the enqueued asynchronous operation didn't finish before the code has
+// a chance to even enqueue the next operation. Delays are chosen so that the
+// test is unlikely to fail in practice. Need to revisit this.
+TEST_P(AsyncQueueTest, CanScheduleOperationsInTheFuture) {
+ std::string steps;
+
+ queue.Enqueue([&steps] { steps += '1'; });
+ queue.Enqueue([&] {
+ queue.EnqueueAfterDelay(AsyncQueue::Milliseconds(20), kTimerId1, [&] {
+ steps += '4';
+ signal_finished();
+ });
+ queue.EnqueueAfterDelay(AsyncQueue::Milliseconds(10), kTimerId2,
+ [&steps] { steps += '3'; });
+ queue.EnqueueRelaxed([&steps] { steps += '2'; });
+ });
+
+ EXPECT_TRUE(WaitForTestToFinish());
+ EXPECT_EQ(steps, "1234");
+}
+
+TEST_P(AsyncQueueTest, CanCancelDelayedOperations) {
+ std::string steps;
+
+ queue.Enqueue([&] {
+ // Queue everything from the queue to ensure nothing completes before we
+ // cancel.
+
+ queue.EnqueueRelaxed([&steps] { steps += '1'; });
+
+ DelayedOperation delayed_operation = queue.EnqueueAfterDelay(
+ AsyncQueue::Milliseconds(1), kTimerId1, [&steps] { steps += '2'; });
+
+ queue.EnqueueAfterDelay(AsyncQueue::Milliseconds(5), kTimerId2, [&] {
+ steps += '3';
+ signal_finished();
+ });
+
+ EXPECT_TRUE(queue.IsScheduled(kTimerId1));
+ delayed_operation.Cancel();
+ EXPECT_FALSE(queue.IsScheduled(kTimerId1));
+ });
+
+ EXPECT_TRUE(WaitForTestToFinish());
+ EXPECT_EQ(steps, "13");
+ EXPECT_FALSE(queue.IsScheduled(kTimerId1));
+}
+
+TEST_P(AsyncQueueTest, CanCallCancelOnDelayedOperationAfterTheOperationHasRun) {
+ DelayedOperation delayed_operation;
+ queue.Enqueue([&] {
+ delayed_operation = queue.EnqueueAfterDelay(
+ AsyncQueue::Milliseconds(10), kTimerId1, [&] { signal_finished(); });
+ EXPECT_TRUE(queue.IsScheduled(kTimerId1));
+ });
+
+ EXPECT_TRUE(WaitForTestToFinish());
+ EXPECT_FALSE(queue.IsScheduled(kTimerId1));
+ EXPECT_NO_THROW(delayed_operation.Cancel());
+}
+
+TEST_P(AsyncQueueTest, CanManuallyDrainAllDelayedOperationsForTesting) {
+ std::string steps;
+
+ queue.Enqueue([&] {
+ queue.EnqueueRelaxed([&steps] { steps += '1'; });
+ queue.EnqueueAfterDelay(AsyncQueue::Milliseconds(20000), kTimerId1,
+ [&] { steps += '4'; });
+ queue.EnqueueAfterDelay(AsyncQueue::Milliseconds(10000), kTimerId2,
+ [&steps] { steps += '3'; });
+ queue.EnqueueRelaxed([&steps] { steps += '2'; });
+ signal_finished();
+ });
+
+ EXPECT_TRUE(WaitForTestToFinish());
+ queue.RunScheduledOperationsUntil(TimerId::All);
+ EXPECT_EQ(steps, "1234");
+}
+
+TEST_P(AsyncQueueTest, CanManuallyDrainSpecificDelayedOperationsForTesting) {
+ std::string steps;
+
+ queue.Enqueue([&] {
+ queue.EnqueueRelaxed([&] { steps += '1'; });
+ queue.EnqueueAfterDelay(AsyncQueue::Milliseconds(20000), kTimerId1,
+ [&steps] { steps += '5'; });
+ queue.EnqueueAfterDelay(AsyncQueue::Milliseconds(10000), kTimerId2,
+ [&steps] { steps += '3'; });
+ queue.EnqueueAfterDelay(AsyncQueue::Milliseconds(15000), kTimerId3,
+ [&steps] { steps += '4'; });
+ queue.EnqueueRelaxed([&] { steps += '2'; });
+ signal_finished();
+ });
+
+ EXPECT_TRUE(WaitForTestToFinish());
+ queue.RunScheduledOperationsUntil(kTimerId3);
+ EXPECT_EQ(steps, "1234");
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/async_queue_test.h b/Firestore/core/test/firebase/firestore/util/async_queue_test.h
new file mode 100644
index 0000000..61c7ab6
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/async_queue_test.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_UTIL_ASYNC_QUEUE_TEST_H_
+#define FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_UTIL_ASYNC_QUEUE_TEST_H_
+
+#include <memory>
+
+#include "gtest/gtest.h"
+
+#include "Firestore/core/src/firebase/firestore/util/async_queue.h"
+#include "Firestore/core/test/firebase/firestore/util/async_tests_util.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+using FactoryFunc = std::unique_ptr<internal::Executor> (*)();
+
+class AsyncQueueTest : public TestWithTimeoutMixin,
+ public ::testing::TestWithParam<FactoryFunc> {
+ public:
+ // `GetParam()` must return a factory function.
+ AsyncQueueTest() : queue{GetParam()()} {
+ }
+
+ AsyncQueue queue;
+};
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_UTIL_ASYNC_QUEUE_TEST_H_
diff --git a/Firestore/core/test/firebase/firestore/util/async_tests_util.h b/Firestore/core/test/firebase/firestore/util/async_tests_util.h
new file mode 100644
index 0000000..f953d66
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/async_tests_util.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_UTIL_ASYNC_TESTS_UTIL_H_
+#define FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_UTIL_ASYNC_TESTS_UTIL_H_
+
+#include <chrono> // NOLINT(build/c++11)
+#include <cstdlib>
+#include <future> // NOLINT(build/c++11)
+
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+inline std::chrono::time_point<std::chrono::steady_clock,
+ std::chrono::milliseconds>
+now() {
+ return std::chrono::time_point_cast<std::chrono::milliseconds>(
+ std::chrono::steady_clock::now());
+}
+
+constexpr auto kTimeout = std::chrono::seconds(5);
+
+// Waits for the future to become ready and returns whether it timed out.
+inline bool Await(const std::future<void>& future,
+ const std::chrono::milliseconds timeout = kTimeout) {
+ return future.wait_for(timeout) == std::future_status::ready;
+}
+
+// Unfortunately, the future returned from std::async blocks in its destructor
+// until the async call is finished. If the function called from std::async is
+// buggy and hangs forever, the future's destructor will also hang forever. To
+// avoid all tests freezing, the only thing to do is to abort (which skips
+// destructors).
+inline void Abort() {
+ ADD_FAILURE();
+ std::abort();
+}
+
+// Calls std::abort if the future times out.
+inline void AbortOnTimeout(const std::future<void>& future) {
+ if (!Await(future, kTimeout)) {
+ Abort();
+ }
+}
+
+// The macro calls AbortOnTimeout, but preserves stack trace.
+#define ABORT_ON_TIMEOUT(future) \
+ do { \
+ SCOPED_TRACE("Async operation timed out, aborting..."); \
+ AbortOnTimeout(future); \
+ } while (0)
+
+class TestWithTimeoutMixin {
+ public:
+ TestWithTimeoutMixin() : signal_finished{[] {}} {
+ }
+
+ // Googletest doesn't contain built-in functionality to block until an async
+ // operation completes, and there is no timeout by default. Work around both
+ // by resolving a packaged_task in the async operation and blocking on the
+ // associated future (with timeout).
+ bool WaitForTestToFinish(const std::chrono::seconds timeout = kTimeout) {
+ return signal_finished.get_future().wait_for(timeout) ==
+ std::future_status::ready;
+ }
+
+ std::packaged_task<void()> signal_finished;
+};
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_UTIL_ASYNC_TESTS_UTIL_H_
diff --git a/Firestore/core/test/firebase/firestore/util/executor_libdispatch_test.mm b/Firestore/core/test/firebase/firestore/util/executor_libdispatch_test.mm
new file mode 100644
index 0000000..330c8fc
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/executor_libdispatch_test.mm
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/test/firebase/firestore/util/executor_test.h"
+
+#include <memory>
+
+#include "Firestore/core/src/firebase/firestore/util/executor_libdispatch.h"
+#include "absl/memory/memory.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+namespace {
+
+std::unique_ptr<internal::Executor> ExecutorFactory() {
+ return absl::make_unique<internal::ExecutorLibdispatch>(
+ dispatch_queue_create("ExecutorLibdispatchTests", DISPATCH_QUEUE_SERIAL));
+}
+
+} // namespace
+
+INSTANTIATE_TEST_CASE_P(ExecutorTestLibdispatch,
+ ExecutorTest,
+ ::testing::Values(ExecutorFactory));
+
+namespace internal {
+class ExecutorLibdispatchOnlyTests : public TestWithTimeoutMixin,
+ public ::testing::Test {
+ public:
+ ExecutorLibdispatchOnlyTests() : executor{ExecutorFactory()} {
+ }
+
+ std::unique_ptr<Executor> executor;
+};
+
+TEST_F(ExecutorLibdispatchOnlyTests, NameReturnsLabelOfTheQueue) {
+ EXPECT_EQ(executor->Name(), "ExecutorLibdispatchTests");
+ executor->Execute([&] {
+ EXPECT_EQ(executor->CurrentExecutorName(), "ExecutorLibdispatchTests");
+ signal_finished();
+ });
+ EXPECT_TRUE(WaitForTestToFinish());
+}
+
+TEST_F(ExecutorLibdispatchOnlyTests,
+ ExecuteBlockingOnTheCurrentQueueIsNotAllowed) {
+ EXPECT_NO_THROW(executor->ExecuteBlocking([] {}));
+ executor->Execute([&] {
+ EXPECT_ANY_THROW(executor->ExecuteBlocking([] {}));
+ signal_finished();
+ });
+ EXPECT_TRUE(WaitForTestToFinish());
+}
+
+} // namespace internal
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/executor_std_test.cc b/Firestore/core/test/firebase/firestore/util/executor_std_test.cc
new file mode 100644
index 0000000..59c3c32
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/executor_std_test.cc
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/test/firebase/firestore/util/executor_test.h"
+
+#include <chrono> // NOLINT(build/c++11)
+#include <cstdlib>
+#include <future> // NOLINT(build/c++11)
+#include <string>
+#include <thread> // NOLINT(build/c++11)
+
+#include "Firestore/core/src/firebase/firestore/util/executor_std.h"
+#include "Firestore/core/test/firebase/firestore/util/async_tests_util.h"
+#include "absl/memory/memory.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+namespace chr = std::chrono;
+using async::Schedule;
+
+class ScheduleTest : public ::testing::Test {
+ public:
+ ScheduleTest() : start_time{now()} {
+ }
+
+ using ScheduleT = Schedule<int>;
+
+ ScheduleT schedule;
+ ScheduleT::TimePoint start_time;
+};
+
+// Schedule tests
+
+TEST_F(ScheduleTest, PopIfDue_Immediate) {
+ EXPECT_FALSE(schedule.PopIfDue().has_value());
+
+ // Push values in a deliberately non-sorted order.
+ schedule.Push(3, start_time);
+ schedule.Push(1, start_time);
+ schedule.Push(2, start_time);
+ EXPECT_FALSE(schedule.empty());
+ EXPECT_EQ(schedule.size(), 3u);
+
+ EXPECT_EQ(schedule.PopIfDue().value(), 3);
+ EXPECT_EQ(schedule.PopIfDue().value(), 1);
+ EXPECT_EQ(schedule.PopIfDue().value(), 2);
+ EXPECT_FALSE(schedule.PopIfDue().has_value());
+ EXPECT_TRUE(schedule.empty());
+ EXPECT_EQ(schedule.size(), 0u);
+}
+
+TEST_F(ScheduleTest, PopIfDue_Delayed) {
+ schedule.Push(1, start_time + chr::milliseconds(5));
+ schedule.Push(2, start_time + chr::milliseconds(3));
+ schedule.Push(3, start_time + chr::milliseconds(1));
+
+ std::this_thread::sleep_for(chr::milliseconds(5));
+
+ EXPECT_EQ(schedule.PopIfDue().value(), 3);
+ EXPECT_EQ(schedule.PopIfDue().value(), 2);
+ EXPECT_EQ(schedule.PopIfDue().value(), 1);
+ EXPECT_TRUE(schedule.empty());
+}
+
+TEST_F(ScheduleTest, PopBlocking) {
+ schedule.Push(1, start_time + chr::milliseconds(3));
+ EXPECT_FALSE(schedule.PopIfDue().has_value());
+
+ EXPECT_EQ(schedule.PopBlocking(), 1);
+ EXPECT_GE(now(), start_time + chr::milliseconds(3));
+ EXPECT_TRUE(schedule.empty());
+}
+
+TEST_F(ScheduleTest, RemoveIf) {
+ schedule.Push(1, start_time);
+ schedule.Push(2, now() + chr::minutes(1));
+
+ auto maybe_removed = schedule.RemoveIf([](const int v) { return v == 1; });
+ EXPECT_TRUE(maybe_removed.has_value());
+ EXPECT_EQ(maybe_removed.value(), 1);
+
+ // Non-existent value.
+ maybe_removed = schedule.RemoveIf([](const int v) { return v == 1; });
+ EXPECT_FALSE(maybe_removed.has_value());
+
+ maybe_removed = schedule.RemoveIf([](const int v) { return v == 2; });
+ EXPECT_TRUE(maybe_removed.has_value());
+ EXPECT_EQ(maybe_removed.value(), 2);
+ EXPECT_TRUE(schedule.empty());
+}
+
+TEST_F(ScheduleTest, Ordering) {
+ schedule.Push(11, start_time + chr::milliseconds(5));
+ schedule.Push(1, start_time);
+ schedule.Push(2, start_time);
+ schedule.Push(9, start_time + chr::milliseconds(2));
+ schedule.Push(3, start_time);
+ schedule.Push(10, start_time + chr::milliseconds(3));
+ schedule.Push(12, start_time + chr::milliseconds(5));
+ schedule.Push(4, start_time);
+ schedule.Push(5, start_time);
+ schedule.Push(6, start_time);
+ schedule.Push(8, start_time + chr::milliseconds(1));
+ schedule.Push(7, start_time);
+
+ std::vector<int> values;
+ while (!schedule.empty()) {
+ values.push_back(schedule.PopBlocking());
+ }
+ const std::vector<int> expected = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+ EXPECT_EQ(values, expected);
+}
+
+TEST_F(ScheduleTest, AddingEntryUnblocksEmptyQueue) {
+ const auto future = std::async(std::launch::async, [&] {
+ ASSERT_FALSE(schedule.PopIfDue().has_value());
+ EXPECT_EQ(schedule.PopBlocking(), 1);
+ });
+
+ std::this_thread::sleep_for(chr::milliseconds(5));
+ schedule.Push(1, start_time);
+ ABORT_ON_TIMEOUT(future);
+}
+
+TEST_F(ScheduleTest, PopBlockingUnblocksOnNewPastDueEntries) {
+ const auto far_away = start_time + chr::seconds(10);
+ schedule.Push(5, far_away);
+
+ const auto future = std::async(std::launch::async, [&] {
+ ASSERT_FALSE(schedule.PopIfDue().has_value());
+ EXPECT_EQ(schedule.PopBlocking(), 3);
+ });
+
+ std::this_thread::sleep_for(chr::milliseconds(5));
+ schedule.Push(3, start_time);
+ ABORT_ON_TIMEOUT(future);
+}
+
+TEST_F(ScheduleTest, PopBlockingAdjustsWaitTimeOnNewSoonerEntries) {
+ const auto far_away = start_time + chr::seconds(10);
+ schedule.Push(5, far_away);
+
+ const auto future = std::async(std::launch::async, [&] {
+ ASSERT_FALSE(schedule.PopIfDue().has_value());
+ EXPECT_EQ(schedule.PopBlocking(), 3);
+ // Make sure schedule hasn't been waiting longer than necessary.
+ EXPECT_LT(now(), far_away);
+ });
+
+ std::this_thread::sleep_for(chr::milliseconds(5));
+ schedule.Push(3, start_time + chr::milliseconds(100));
+ ABORT_ON_TIMEOUT(future);
+}
+
+TEST_F(ScheduleTest, PopBlockingCanReadjustTimeIfSeveralElementsAreAdded) {
+ const auto far_away = start_time + chr::seconds(5);
+ const auto very_far_away = start_time + chr::seconds(10);
+ schedule.Push(3, very_far_away);
+
+ const auto future = std::async(std::launch::async, [&] {
+ ASSERT_FALSE(schedule.PopIfDue().has_value());
+ EXPECT_EQ(schedule.PopBlocking(), 1);
+ EXPECT_LT(now(), far_away);
+ });
+
+ std::this_thread::sleep_for(chr::milliseconds(5));
+ schedule.Push(2, far_away);
+ std::this_thread::sleep_for(chr::milliseconds(1));
+ schedule.Push(1, start_time + chr::milliseconds(100));
+ ABORT_ON_TIMEOUT(future);
+}
+
+TEST_F(ScheduleTest, PopBlockingNoticesRemovals) {
+ const auto future = std::async(std::launch::async, [&] {
+ schedule.Push(1, start_time + chr::milliseconds(50));
+ schedule.Push(2, start_time + chr::milliseconds(100));
+ ASSERT_FALSE(schedule.PopIfDue().has_value());
+ EXPECT_EQ(schedule.PopBlocking(), 2);
+ });
+
+ while (schedule.empty()) {
+ std::this_thread::sleep_for(chr::milliseconds(1));
+ }
+ const auto maybe_removed =
+ schedule.RemoveIf([](const int v) { return v == 1; });
+ EXPECT_EQ(maybe_removed.value(), 1);
+ ABORT_ON_TIMEOUT(future);
+}
+
+TEST_F(ScheduleTest, PopBlockingIsNotAffectedByIrrelevantRemovals) {
+ const auto future = std::async(std::launch::async, [&] {
+ schedule.Push(1, start_time + chr::milliseconds(50));
+ schedule.Push(2, start_time + chr::seconds(10));
+ ASSERT_FALSE(schedule.PopIfDue().has_value());
+ EXPECT_EQ(schedule.PopBlocking(), 1);
+ });
+
+ // Wait (with timeout) for both values to appear in the schedule.
+ while (schedule.size() != 2) {
+ if (now() - start_time >= kTimeout) {
+ Abort();
+ }
+ std::this_thread::sleep_for(chr::milliseconds(1));
+ }
+ const auto maybe_removed =
+ schedule.RemoveIf([](const int v) { return v == 2; });
+ ASSERT_TRUE(maybe_removed.has_value());
+ EXPECT_EQ(maybe_removed.value(), 2);
+ ABORT_ON_TIMEOUT(future);
+}
+
+// ExecutorStd tests
+
+namespace {
+
+inline std::unique_ptr<internal::Executor> ExecutorFactory() {
+ return absl::make_unique<internal::ExecutorStd>();
+}
+
+} // namespace
+
+INSTANTIATE_TEST_CASE_P(ExecutorTestStd,
+ ExecutorTest,
+ ::testing::Values(ExecutorFactory));
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/executor_test.cc b/Firestore/core/test/firebase/firestore/util/executor_test.cc
new file mode 100644
index 0000000..99bddce
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/executor_test.cc
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/test/firebase/firestore/util/executor_test.h"
+
+#include <chrono> // NOLINT(build/c++11)
+#include <cstdlib>
+#include <future> // NOLINT(build/c++11)
+#include <string>
+#include <thread> // NOLINT(build/c++11)
+
+#include "Firestore/core/src/firebase/firestore/util/executor.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+namespace chr = std::chrono;
+using internal::Executor;
+
+namespace {
+
+DelayedOperation Schedule(Executor* const executor,
+ const Executor::Milliseconds delay,
+ Executor::Operation&& operation) {
+ const Executor::Tag no_tag = -1;
+ return executor->Schedule(
+ delay, Executor::TaggedOperation{no_tag, std::move(operation)});
+}
+
+} // namespace
+
+TEST_P(ExecutorTest, Execute) {
+ executor->Execute([&] { signal_finished(); });
+ EXPECT_TRUE(WaitForTestToFinish());
+}
+
+TEST_P(ExecutorTest, ExecuteBlocking) {
+ bool finished = false;
+ executor->ExecuteBlocking([&] { finished = true; });
+ EXPECT_TRUE(finished);
+}
+
+TEST_P(ExecutorTest, DestructorDoesNotBlockIfThereArePendingTasks) {
+ const auto future = std::async(std::launch::async, [&] {
+ auto another_executor = GetParam()();
+ Schedule(another_executor.get(), chr::minutes(5), [] {});
+ Schedule(another_executor.get(), chr::minutes(10), [] {});
+ // Destructor shouldn't block waiting for the 5/10-minute-away operations.
+ });
+
+ ABORT_ON_TIMEOUT(future);
+}
+
+// TODO(varconst): this test is inherently flaky because it can't be guaranteed
+// that the enqueued asynchronous operation didn't finish before the code has
+// a chance to even enqueue the next operation. Delays are chosen so that the
+// test is unlikely to fail in practice. Need to revisit this.
+TEST_P(ExecutorTest, CanScheduleOperationsInTheFuture) {
+ std::string steps;
+
+ executor->Execute([&steps] { steps += '1'; });
+ Schedule(executor.get(), Executor::Milliseconds(20), [&] {
+ steps += '4';
+ signal_finished();
+ });
+ Schedule(executor.get(), Executor::Milliseconds(10),
+ [&steps] { steps += '3'; });
+ executor->Execute([&steps] { steps += '2'; });
+
+ EXPECT_TRUE(WaitForTestToFinish());
+ EXPECT_EQ(steps, "1234");
+}
+
+TEST_P(ExecutorTest, CanCancelDelayedOperations) {
+ std::string steps;
+
+ executor->Execute([&] {
+ executor->Execute([&steps] { steps += '1'; });
+
+ DelayedOperation delayed_operation = Schedule(
+ executor.get(), Executor::Milliseconds(1), [&steps] { steps += '2'; });
+
+ Schedule(executor.get(), Executor::Milliseconds(5), [&] {
+ steps += '3';
+ signal_finished();
+ });
+
+ delayed_operation.Cancel();
+ });
+
+ EXPECT_TRUE(WaitForTestToFinish());
+ EXPECT_EQ(steps, "13");
+}
+
+TEST_P(ExecutorTest, DelayedOperationIsValidAfterTheOperationHasRun) {
+ DelayedOperation delayed_operation = Schedule(
+ executor.get(), Executor::Milliseconds(1), [&] { signal_finished(); });
+
+ EXPECT_TRUE(WaitForTestToFinish());
+ EXPECT_NO_THROW(delayed_operation.Cancel());
+}
+
+TEST_P(ExecutorTest, IsCurrentExecutor) {
+ EXPECT_FALSE(executor->IsCurrentExecutor());
+ EXPECT_NE(executor->Name(), executor->CurrentExecutorName());
+
+ executor->ExecuteBlocking([&] {
+ EXPECT_TRUE(executor->IsCurrentExecutor());
+ EXPECT_EQ(executor->Name(), executor->CurrentExecutorName());
+ });
+
+ executor->Execute([&] {
+ EXPECT_TRUE(executor->IsCurrentExecutor());
+ EXPECT_EQ(executor->Name(), executor->CurrentExecutorName());
+ });
+
+ Schedule(executor.get(), Executor::Milliseconds(1), [&] {
+ EXPECT_TRUE(executor->IsCurrentExecutor());
+ EXPECT_EQ(executor->Name(), executor->CurrentExecutorName());
+ signal_finished();
+ });
+
+ EXPECT_TRUE(WaitForTestToFinish());
+}
+
+TEST_P(ExecutorTest, OperationsCanBeRemovedFromScheduleBeforeTheyRun) {
+ const Executor::Tag tag_foo = 1;
+ const Executor::Tag tag_bar = 2;
+
+ // Make sure the schedule is empty.
+ EXPECT_FALSE(executor->IsScheduled(tag_foo));
+ EXPECT_FALSE(executor->IsScheduled(tag_bar));
+ EXPECT_FALSE(executor->PopFromSchedule().has_value());
+
+ // Add two operations to the schedule with different tags.
+
+ // The exact delay doesn't matter as long as it's too far away to be executed
+ // during the test.
+ const auto far_away = chr::seconds(1);
+ executor->Schedule(far_away, {tag_foo, [] {}});
+ // Scheduled operations can be distinguished by their tag.
+ EXPECT_TRUE(executor->IsScheduled(tag_foo));
+ EXPECT_FALSE(executor->IsScheduled(tag_bar));
+
+ // This operation will be scheduled after the previous one (operations
+ // scheduled with the same delay are FIFO ordered).
+ executor->Schedule(far_away, {tag_bar, [] {}});
+ EXPECT_TRUE(executor->IsScheduled(tag_foo));
+ EXPECT_TRUE(executor->IsScheduled(tag_bar));
+
+ // Now pop the operations one by one without waiting for them to be executed,
+ // check that operations are popped in the order they are scheduled and
+ // preserve tags. Schedule should become empty as a result.
+
+ auto maybe_operation = executor->PopFromSchedule();
+ ASSERT_TRUE(maybe_operation.has_value());
+ EXPECT_EQ(maybe_operation->tag, tag_foo);
+ EXPECT_FALSE(executor->IsScheduled(tag_foo));
+ EXPECT_TRUE(executor->IsScheduled(tag_bar));
+
+ maybe_operation = executor->PopFromSchedule();
+ ASSERT_TRUE(maybe_operation.has_value());
+ EXPECT_EQ(maybe_operation->tag, tag_bar);
+ EXPECT_FALSE(executor->IsScheduled(tag_bar));
+
+ // Schedule should now be empty.
+ EXPECT_FALSE(executor->PopFromSchedule().has_value());
+}
+
+TEST_P(ExecutorTest, DuplicateTagsOnOperationsAreAllowed) {
+ const Executor::Tag tag_foo = 1;
+ std::string steps;
+
+ // Add two operations with the same tag to the schedule to verify that
+ // duplicate tags are allowed.
+
+ const auto far_away = chr::seconds(1);
+ executor->Schedule(far_away, {tag_foo, [&steps] { steps += '1'; }});
+ executor->Schedule(far_away, {tag_foo, [&steps] { steps += '2'; }});
+ EXPECT_TRUE(executor->IsScheduled(tag_foo));
+
+ auto maybe_operation = executor->PopFromSchedule();
+ ASSERT_TRUE(maybe_operation.has_value());
+ EXPECT_EQ(maybe_operation->tag, tag_foo);
+ // There's still another operation with the same tag in the schedule.
+ EXPECT_TRUE(executor->IsScheduled(tag_foo));
+
+ maybe_operation->operation();
+
+ maybe_operation = executor->PopFromSchedule();
+ ASSERT_TRUE(maybe_operation.has_value());
+ EXPECT_EQ(maybe_operation->tag, tag_foo);
+ EXPECT_FALSE(executor->IsScheduled(tag_foo));
+
+ maybe_operation->operation();
+ // Despite having the same tag, the operations should have been ordered
+ // according to their scheduled time and preserved their identity.
+ EXPECT_EQ(steps, "12");
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Firestore/core/test/firebase/firestore/util/executor_test.h b/Firestore/core/test/firebase/firestore/util/executor_test.h
new file mode 100644
index 0000000..8b78d50
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/executor_test.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_UTIL_EXECUTOR_TEST_H_
+#define FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_UTIL_EXECUTOR_TEST_H_
+
+#include <memory>
+
+#include "gtest/gtest.h"
+
+#include "Firestore/core/src/firebase/firestore/util/executor.h"
+#include "Firestore/core/test/firebase/firestore/util/async_tests_util.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+using FactoryFunc = std::unique_ptr<internal::Executor> (*)();
+
+class ExecutorTest : public TestWithTimeoutMixin,
+ public ::testing::TestWithParam<FactoryFunc> {
+ public:
+ // `GetParam()` must return a factory function.
+ ExecutorTest() : executor{GetParam()()} {
+ }
+
+ std::unique_ptr<internal::Executor> executor;
+};
+} // namespace util
+} // namespace firestore
+} // namespace firebase
+
+#endif // FIRESTORE_CORE_TEST_FIREBASE_FIRESTORE_UTIL_EXECUTOR_TEST_H_
diff --git a/Firestore/core/test/firebase/firestore/util/hashing_test.cc b/Firestore/core/test/firebase/firestore/util/hashing_test.cc
new file mode 100644
index 0000000..e5d9ff8
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/hashing_test.cc
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2018 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/src/firebase/firestore/util/hashing.h"
+
+#include "absl/strings/string_view.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace util {
+
+struct HasHashMember {
+ size_t Hash() const {
+ return 42;
+ }
+};
+
+TEST(HashingTest, Int) {
+ ASSERT_EQ(std::hash<int>{}(0), Hash(0));
+}
+
+TEST(HashingTest, Float) {
+ ASSERT_EQ(std::hash<double>{}(1.0), Hash(1.0));
+}
+
+TEST(HashingTest, String) {
+ ASSERT_EQ(std::hash<std::string>{}("foobar"), Hash(std::string{"foobar"}));
+}
+
+TEST(HashingTest, StringView) {
+ // For StringView we expect the range-based hasher to kick in. This is
+ // basically terrible, but no worse than Java's `String.hashCode()`. Another
+ // possibility would be just to create a temporary std::string and std::hash
+ // that, but that requires an explicit specialization. Since we're only
+ // defining this for compatibility with Objective-C and not really sensitive
+ // to performance or hash quality here, this is good enough.
+ size_t expected = 'a';
+ expected = 31u * expected + 1;
+ ASSERT_EQ(expected, Hash(absl::string_view{"a"}));
+}
+
+TEST(HashingTest, SizeT) {
+ ASSERT_EQ(42u, Hash(size_t{42u}));
+}
+
+TEST(HashingTest, Array) {
+ int values[] = {0, 1, 2};
+
+ size_t expected = 0;
+ expected = 31 * expected + 1;
+ expected = 31 * expected + 2;
+ expected = 31 * expected + 3; // length of array
+ ASSERT_EQ(expected, Hash(values));
+}
+
+TEST(HashingTest, HasHashMember) {
+ ASSERT_EQ(static_cast<size_t>(42), Hash(HasHashMember{}));
+}
+
+TEST(HashingTest, RangeOfStdHashable) {
+ std::vector<int> values{42};
+ ASSERT_EQ(31u * 42u + 1, Hash(values));
+
+ std::vector<int> values_leading_zero{0, 42};
+ std::vector<int> values_trailing_zero{42, 0};
+
+ EXPECT_NE(Hash(values), Hash(values_leading_zero));
+ EXPECT_NE(Hash(values), Hash(values_trailing_zero));
+ EXPECT_NE(Hash(values_leading_zero), Hash(values_trailing_zero));
+}
+
+TEST(HashingTest, RangeOfHashMember) {
+ std::vector<HasHashMember> values{HasHashMember{}};
+ ASSERT_EQ(31u * 42u + 1, Hash(values));
+}
+
+TEST(HashingTest, Composite) {
+ // Verify the result ends up as if hand-rolled
+ EXPECT_EQ(1u, Hash(1));
+ EXPECT_EQ(31u, Hash(1, 0));
+ EXPECT_EQ(31u * 31u, Hash(1, 0, 0));
+
+ size_t expected = Hash(1);
+ expected = 31 * expected + Hash(2);
+ expected = 31 * expected + Hash(3);
+ EXPECT_EQ(expected, Hash(1, 2, 3));
+}
+
+} // namespace util
+} // namespace firestore
+} // namespace firebase
diff --git a/Functions/Example/FirebaseFunctions.xcodeproj/project.pbxproj b/Functions/Example/FirebaseFunctions.xcodeproj/project.pbxproj
index d70f4b5..e018981 100644
--- a/Functions/Example/FirebaseFunctions.xcodeproj/project.pbxproj
+++ b/Functions/Example/FirebaseFunctions.xcodeproj/project.pbxproj
@@ -20,16 +20,12 @@
6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
6003F5BC195388D20070C39A /* FIRFunctionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5BB195388D20070C39A /* FIRFunctionsTests.m */; };
- 633D50991C0FDF68085030B5 /* Pods_FirebaseFunctions_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B954281766FFD7C7C9970E9B /* Pods_FirebaseFunctions_Example.framework */; };
- 6358342A9F1AF395C6C52471 /* Pods_FirebaseFunctions_IntegrationTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A4C1781DC97515D32B33DBD /* Pods_FirebaseFunctions_IntegrationTests.framework */; };
71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; };
- 78F883E44C88CC18414F3DAF /* Pods_FirebaseFunctions_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A19679D6C21E3206B4A13697 /* Pods_FirebaseFunctions_Tests.framework */; };
7C58B03A1F1441F0005ED954 /* FUNSerializerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7C58B0391F1441F0005ED954 /* FUNSerializerTests.m */; };
7CBFAA82205702AB00A65866 /* FIRIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 7C9BFD3F1F10A12F001A19ED /* FIRIntegrationTests.m */; };
7CBFAA86205702AB00A65866 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
7CBFAA87205702AB00A65866 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
7CBFAA88205702AB00A65866 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
- 7CBFAA89205702AB00A65866 /* Pods_FirebaseFunctions_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A19679D6C21E3206B4A13697 /* Pods_FirebaseFunctions_Tests.framework */; };
7CBFAA8B205702AB00A65866 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
7CF3BEC21F97EE2F00B16B6E /* FUNFakeInstanceID.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CF3BEC01F97EE2F00B16B6E /* FUNFakeInstanceID.m */; };
7CF563091F1FE70600FEE1F4 /* FUNFakeApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 7CF563081F1FE70600FEE1F4 /* FUNFakeApp.m */; };
@@ -54,10 +50,6 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
- 0A4C1781DC97515D32B33DBD /* Pods_FirebaseFunctions_IntegrationTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FirebaseFunctions_IntegrationTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- 501CFEC7487BD4C12FCEAB03 /* Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_IntegrationTests/Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig"; sourceTree = "<group>"; };
- 59519EC771AF4770F85B6534 /* Pods-FirebaseFunctions_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_Example.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example.debug.xcconfig"; sourceTree = "<group>"; };
- 5B43C813C653DE3455952E9B /* Pods-FirebaseFunctions_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_Tests/Pods-FirebaseFunctions_Tests.release.xcconfig"; sourceTree = "<group>"; };
6003F58A195388D20070C39A /* FirebaseFunctions_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FirebaseFunctions_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
@@ -86,13 +78,8 @@
7CF563081F1FE70600FEE1F4 /* FUNFakeApp.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FUNFakeApp.m; sourceTree = "<group>"; };
7CF5630A1F1FE76700FEE1F4 /* FUNFakeApp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FUNFakeApp.h; sourceTree = "<group>"; };
873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
- A19679D6C21E3206B4A13697 /* Pods_FirebaseFunctions_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FirebaseFunctions_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
- B954281766FFD7C7C9970E9B /* Pods_FirebaseFunctions_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FirebaseFunctions_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E0A8D570636E99E7C3396DF8 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
- E2E6249788F1618471335BE7 /* Pods-FirebaseFunctions_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_Example.release.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example.release.xcconfig"; sourceTree = "<group>"; };
F1F2A7C03C10A3A03F9502B8 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
- F840F9EDCB950E7509527AB7 /* Pods-FirebaseFunctions_IntegrationTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_IntegrationTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_IntegrationTests/Pods-FirebaseFunctions_IntegrationTests.release.xcconfig"; sourceTree = "<group>"; };
- FFE894EDD87F1EF24F0ED8DD /* Pods-FirebaseFunctions_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FirebaseFunctions_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-FirebaseFunctions_Tests/Pods-FirebaseFunctions_Tests.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -103,7 +90,6 @@
6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */,
6003F592195388D20070C39A /* UIKit.framework in Frameworks */,
6003F58E195388D20070C39A /* Foundation.framework in Frameworks */,
- 633D50991C0FDF68085030B5 /* Pods_FirebaseFunctions_Example.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -114,7 +100,6 @@
6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */,
6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */,
6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */,
- 78F883E44C88CC18414F3DAF /* Pods_FirebaseFunctions_Tests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -125,27 +110,12 @@
7CBFAA86205702AB00A65866 /* XCTest.framework in Frameworks */,
7CBFAA87205702AB00A65866 /* UIKit.framework in Frameworks */,
7CBFAA88205702AB00A65866 /* Foundation.framework in Frameworks */,
- 7CBFAA89205702AB00A65866 /* Pods_FirebaseFunctions_Tests.framework in Frameworks */,
- 6358342A9F1AF395C6C52471 /* Pods_FirebaseFunctions_IntegrationTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
- 004DC60D1A62294B06E83453 /* Pods */ = {
- isa = PBXGroup;
- children = (
- 59519EC771AF4770F85B6534 /* Pods-FirebaseFunctions_Example.debug.xcconfig */,
- E2E6249788F1618471335BE7 /* Pods-FirebaseFunctions_Example.release.xcconfig */,
- FFE894EDD87F1EF24F0ED8DD /* Pods-FirebaseFunctions_Tests.debug.xcconfig */,
- 5B43C813C653DE3455952E9B /* Pods-FirebaseFunctions_Tests.release.xcconfig */,
- 501CFEC7487BD4C12FCEAB03 /* Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig */,
- F840F9EDCB950E7509527AB7 /* Pods-FirebaseFunctions_IntegrationTests.release.xcconfig */,
- );
- name = Pods;
- sourceTree = "<group>";
- };
6003F581195388D10070C39A = {
isa = PBXGroup;
children = (
@@ -156,7 +126,6 @@
6003F5B5195388D20070C39A /* Tests */,
6003F58C195388D20070C39A /* Frameworks */,
6003F58B195388D20070C39A /* Products */,
- 004DC60D1A62294B06E83453 /* Pods */,
7CBFAA92205702AC00A65866 /* FirebaseFunctions_Tests copy-Info.plist */,
);
sourceTree = "<group>";
@@ -178,9 +147,6 @@
6003F58F195388D20070C39A /* CoreGraphics.framework */,
6003F591195388D20070C39A /* UIKit.framework */,
6003F5AF195388D20070C39A /* XCTest.framework */,
- B954281766FFD7C7C9970E9B /* Pods_FirebaseFunctions_Example.framework */,
- A19679D6C21E3206B4A13697 /* Pods_FirebaseFunctions_Tests.framework */,
- 0A4C1781DC97515D32B33DBD /* Pods_FirebaseFunctions_IntegrationTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -266,12 +232,9 @@
isa = PBXNativeTarget;
buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "FirebaseFunctions_Example" */;
buildPhases = (
- C1B1E870DA1D732EC2F1F946 /* [CP] Check Pods Manifest.lock */,
6003F586195388D20070C39A /* Sources */,
6003F587195388D20070C39A /* Frameworks */,
6003F588195388D20070C39A /* Resources */,
- BB7A7FC40453827CC7541BB7 /* [CP] Embed Pods Frameworks */,
- 0409BC62B2C369D14416E480 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -286,12 +249,9 @@
isa = PBXNativeTarget;
buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "FirebaseFunctions_Tests" */;
buildPhases = (
- D9E332DA9E27ECA02C2B45D2 /* [CP] Check Pods Manifest.lock */,
6003F5AA195388D20070C39A /* Sources */,
6003F5AB195388D20070C39A /* Frameworks */,
6003F5AC195388D20070C39A /* Resources */,
- 3B7914C40161367B84BC9E52 /* [CP] Embed Pods Frameworks */,
- F0A35EE9B43049BA133AD88F /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -307,12 +267,9 @@
isa = PBXNativeTarget;
buildConfigurationList = 7CBFAA8E205702AB00A65866 /* Build configuration list for PBXNativeTarget "FirebaseFunctions_IntegrationTests" */;
buildPhases = (
- 7CBFAA80205702AB00A65866 /* [CP] Check Pods Manifest.lock */,
7CBFAA81205702AB00A65866 /* Sources */,
7CBFAA85205702AB00A65866 /* Frameworks */,
7CBFAA8A205702AB00A65866 /* Resources */,
- 7CBFAA8C205702AB00A65866 /* [CP] Embed Pods Frameworks */,
- 7CBFAA8D205702AB00A65866 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
@@ -389,158 +346,6 @@
};
/* End PBXResourcesBuildPhase section */
-/* Begin PBXShellScriptBuildPhase section */
- 0409BC62B2C369D14416E480 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 3B7914C40161367B84BC9E52 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Tests/Pods-FirebaseFunctions_Tests-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 7CBFAA80205702AB00A65866 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-FirebaseFunctions_IntegrationTests-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- 7CBFAA8C205702AB00A65866 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_IntegrationTests/Pods-FirebaseFunctions_IntegrationTests-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- 7CBFAA8D205702AB00A65866 /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_IntegrationTests/Pods-FirebaseFunctions_IntegrationTests-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
- BB7A7FC40453827CC7541BB7 /* [CP] Embed Pods Frameworks */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
- "${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GTMSessionFetcher.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleToolboxForMac.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Example/Pods-FirebaseFunctions_Example-frameworks.sh\"\n";
- showEnvVarsInLog = 0;
- };
- C1B1E870DA1D732EC2F1F946 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-FirebaseFunctions_Example-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- D9E332DA9E27ECA02C2B45D2 /* [CP] Check Pods Manifest.lock */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
- "${PODS_ROOT}/Manifest.lock",
- );
- name = "[CP] Check Pods Manifest.lock";
- outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-FirebaseFunctions_Tests-checkManifestLockResult.txt",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
- showEnvVarsInLog = 0;
- };
- F0A35EE9B43049BA133AD88F /* [CP] Copy Pods Resources */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- );
- name = "[CP] Copy Pods Resources";
- outputPaths = (
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-FirebaseFunctions_Tests/Pods-FirebaseFunctions_Tests-resources.sh\"\n";
- showEnvVarsInLog = 0;
- };
-/* End PBXShellScriptBuildPhase section */
-
/* Begin PBXSourcesBuildPhase section */
6003F586195388D20070C39A /* Sources */ = {
isa = PBXSourcesBuildPhase;
@@ -689,12 +494,15 @@
};
6003F5C0195388D20070C39A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 59519EC771AF4770F85B6534 /* Pods-FirebaseFunctions_Example.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
INFOPLIST_FILE = "FirebaseFunctions/FirebaseFunctions-Info.plist";
MODULE_NAME = ExampleApp;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
@@ -703,12 +511,15 @@
};
6003F5C1195388D20070C39A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = E2E6249788F1618471335BE7 /* Pods-FirebaseFunctions_Example.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
GCC_PRECOMPILE_PREFIX_HEADER = NO;
INFOPLIST_FILE = "FirebaseFunctions/FirebaseFunctions-Info.plist";
MODULE_NAME = ExampleApp;
+ OTHER_LDFLAGS = (
+ "$(inherited)",
+ "-all_load",
+ );
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
WRAPPER_EXTENSION = app;
@@ -717,7 +528,6 @@
};
6003F5C3195388D20070C39A /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = FFE894EDD87F1EF24F0ED8DD /* Pods-FirebaseFunctions_Tests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
FRAMEWORK_SEARCH_PATHS = (
@@ -741,7 +551,6 @@
};
6003F5C4195388D20070C39A /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 5B43C813C653DE3455952E9B /* Pods-FirebaseFunctions_Tests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
FRAMEWORK_SEARCH_PATHS = (
@@ -761,7 +570,6 @@
};
7CBFAA8F205702AB00A65866 /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 501CFEC7487BD4C12FCEAB03 /* Pods-FirebaseFunctions_IntegrationTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
FRAMEWORK_SEARCH_PATHS = (
@@ -785,7 +593,6 @@
};
7CBFAA90205702AB00A65866 /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = F840F9EDCB950E7509527AB7 /* Pods-FirebaseFunctions_IntegrationTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
FRAMEWORK_SEARCH_PATHS = (
diff --git a/Functions/README.md b/Functions/README.md
index f0b8d62..b35f507 100644
--- a/Functions/README.md
+++ b/Functions/README.md
@@ -1,10 +1,22 @@
# Cloud Functions for Firebase iOS SDK
-## To run unit tests
+## Development
+
+Follow the subsequent instructions to develop, debug, unit test, and
+integration test FirebaseFunctions:
+
+```
+$ git clone git@github.com:firebase/firebase-ios-sdk.git
+$ cd firebase-ios-sdk/Functions/Example
+$ pod update
+$ open FirebaseFunctions.xcworkspace
+```
+
+### Running Unit Tests
Choose the FirebaseFunctions_Tests scheme and press Command-u.
-## To run integration tests
+## Running Integration Tests
Before running the integration tests, you'll need to start a backend emulator
for them to talk to.
diff --git a/Gemfile.lock b/Gemfile.lock
index e553857..7a0065e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,13 +1,13 @@
GIT
remote: https://github.com/CocoaPods/CocoaPods.git
- revision: df6872de04fd1808fde71c824530e3a784e6a2d2
+ revision: 7d6405dc743082f511f0935bc0f66268f202345d
specs:
- cocoapods (1.4.0)
+ cocoapods (1.5.2)
activesupport (>= 4.0.2, < 5)
claide (>= 1.0.2, < 2.0)
- cocoapods-core (= 1.4.0)
+ cocoapods-core (= 1.5.2)
cocoapods-deintegrate (>= 1.0.2, < 2.0)
- cocoapods-downloader (>= 1.1.3, < 2.0)
+ cocoapods-downloader (>= 1.2.0, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-stats (>= 1.0.0, < 2.0)
@@ -17,44 +17,44 @@ GIT
escape (~> 0.0.4)
fourflusher (~> 2.0.1)
gh_inspector (~> 1.0)
- molinillo (~> 0.6.4)
+ molinillo (~> 0.6.5)
nap (~> 1.0)
ruby-macho (~> 1.1)
- xcodeproj (>= 1.5.4, < 2.0)
+ xcodeproj (>= 1.5.8, < 2.0)
GIT
remote: https://github.com/CocoaPods/Core.git
- revision: b4fb2f193897c789c094d126ebca91034edc261d
+ revision: 0840691058b9fe96f759dacc26642207beb050cd
specs:
- cocoapods-core (1.4.0)
+ cocoapods-core (1.5.2)
activesupport (>= 4.0.2, < 6)
fuzzy_match (~> 2.0.4)
nap (~> 1.0)
GIT
remote: https://github.com/CocoaPods/Xcodeproj.git
- revision: 531d8aa8ab4805c84f5e0fe94181ae3995430d05
+ revision: 3333a879ecd24c42ebb6fc99ce4897ee099d4ce3
specs:
- xcodeproj (1.5.4)
- CFPropertyList (~> 2.3.3)
- atomos (~> 0.1.0)
+ xcodeproj (1.5.8)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.2)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
- nanaimo (~> 0.2.3)
+ nanaimo (~> 0.2.5)
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (2.3.6)
+ CFPropertyList (3.0.0)
activesupport (4.2.10)
i18n (~> 0.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
- atomos (0.1.0)
+ atomos (0.1.2)
claide (1.0.2)
cocoapods-deintegrate (1.0.2)
- cocoapods-downloader (1.1.3)
+ cocoapods-downloader (1.2.0)
cocoapods-plugins (1.0.0)
nap
cocoapods-search (1.0.0)
@@ -68,17 +68,17 @@ GEM
escape (0.0.4)
fourflusher (2.0.1)
fuzzy_match (2.0.4)
- gh_inspector (1.0.3)
- i18n (0.9.3)
+ gh_inspector (1.1.3)
+ i18n (0.9.5)
concurrent-ruby (~> 1.0)
- minitest (5.11.1)
- molinillo (0.6.4)
- nanaimo (0.2.3)
+ minitest (5.11.3)
+ molinillo (0.6.5)
+ nanaimo (0.2.5)
nap (1.1.0)
netrc (0.11.0)
ruby-macho (1.1.0)
thread_safe (0.3.6)
- tzinfo (1.2.4)
+ tzinfo (1.2.5)
thread_safe (~> 0.1)
PLATFORMS
@@ -90,4 +90,4 @@ DEPENDENCIES
xcodeproj!
BUNDLED WITH
- 1.16.0
+ 1.16.1
diff --git a/scripts/build.sh b/scripts/build.sh
index ef10059..4d6c230 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -84,7 +84,7 @@ case "$platform" in
iOS)
xcb_flags=(
-sdk 'iphonesimulator'
- -destination 'platform=iOS Simulator,name=iPhone 7'
+ -destination 'platform=iOS Simulator,name=iPhone 8 Plus'
)
;;
@@ -111,6 +111,7 @@ esac
xcb_flags+=(
ONLY_ACTIVE_ARCH=YES
CODE_SIGNING_REQUIRED=NO
+ CODE_SIGNING_ALLOWED=YES
)
# TODO(varconst): --warn-unused-vars - right now, it makes the log overflow on
@@ -179,10 +180,6 @@ case "$product-$method-$platform" in
cd Example
sed -i -e 's/use_frameworks/\#use_frameworks/' Podfile
pod update --no-repo-update
- # Workarounds for https://github.com/CocoaPods/CocoaPods/issues/7592.
- # Remove when updating to CocoaPods 1.5.1
- sed -i -e 's/-l"FirebaseMessaging"//' "Pods/Target Support Files/Pods-Messaging_Tests_iOS/Pods-Messaging_Tests_iOS.debug.xcconfig"
- sed -i -e 's/-l"FirebaseAuth-iOS" -l"FirebaseCore-iOS"//' "Pods/Target Support Files/Pods-Auth_Tests_iOS/Pods-Auth_Tests_iOS.debug.xcconfig"
cd ..
RunXcodebuild \
-workspace 'Example/Firebase.xcworkspace' \
@@ -214,6 +211,12 @@ case "$product-$method-$platform" in
RunXcodebuild \
-workspace 'Firestore/Example/Firestore.xcworkspace' \
+ -scheme "Firestore_IntegrationTests_$platform" \
+ "${xcb_flags[@]}" \
+ build
+
+ RunXcodebuild \
+ -workspace 'Firestore/Example/Firestore.xcworkspace' \
-scheme 'SwiftBuildTest' \
"${xcb_flags[@]}" \
build
diff --git a/scripts/check_test_inclusion.py b/scripts/check_test_inclusion.py
new file mode 100755
index 0000000..7f5f354
--- /dev/null
+++ b/scripts/check_test_inclusion.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python
+
+# Copyright 2018 Google
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Verifies that all tests are a part of the project file.
+"""
+
+from __future__ import print_function
+import os
+import os.path
+import re
+import sys
+
+
+# Tests that are known not to compile in Xcode and can't be added there.
+EXCLUDED = frozenset([
+ # b/79496027
+ "Firestore/core/test/firebase/firestore/remote/serializer_test.cc",
+])
+
+
+def Main():
+ """Runs the style check."""
+
+ tests = FindTestFiles("Firestore/Example/Tests", "Firestore/core/test")
+ problems = CheckProject(
+ "Firestore/Example/Firestore.xcodeproj/project.pbxproj", tests)
+
+ if problems:
+ Error("Test files exist that are unreferenced in Xcode project files:")
+ for problem in problems:
+ Error(problem)
+ sys.exit(1)
+
+ sys.exit(0)
+
+
+def FindTestFiles(*test_dirs):
+ """Searches the given source roots for test files.
+
+ Args:
+ *test_dirs: A list of directories containing test sources.
+
+ Returns:
+ A list of test source filenames.
+ """
+
+ test_file_pattern = re.compile(r"(?:Tests?\.mm?|_test\.(?:cc|mm))$")
+
+ result = []
+ for test_dir in test_dirs:
+ for root, dirs, files in os.walk(test_dir):
+ del dirs # unused
+ for basename in files:
+ filename = os.path.join(root, basename)
+ if filename not in EXCLUDED and test_file_pattern.search(basename):
+ result.append(filename)
+ return result
+
+
+def CheckProject(project_file, test_files):
+ """Checks the given project file for tests in the given test_dirs.
+
+ Args:
+ project_file: The path to an Xcode pbxproj file.
+ test_files: A list of all tests source files in the project.
+
+ Returns:
+ A sorted list of filenames that aren't referenced in the project_file.
+ """
+
+ # An dict of basename to filename
+ basenames = {os.path.basename(f) : f for f in test_files}
+
+ file_list_pattern = re.compile(r"/\* (\S+) in Sources \*/")
+ with open(project_file, "r") as fd:
+ for line in fd:
+ line = line.rstrip()
+ m = file_list_pattern.search(line)
+ if m:
+ basename = m.group(1)
+ if basename in basenames:
+ del basenames[basename]
+
+ return sorted(basenames.values())
+
+
+def Error(message, *args):
+ message %= args
+ print(message, file=sys.stderr)
+
+
+if __name__ == "__main__":
+ Main()
diff --git a/scripts/if_changed.sh b/scripts/if_changed.sh
index 697cad2..51217d6 100755
--- a/scripts/if_changed.sh
+++ b/scripts/if_changed.sh
@@ -49,7 +49,7 @@ else
check_changes '^(Firebase|Functions|Example)'
;;
- Firestore-xcodebuild)
+ Firestore-xcodebuild|Firestore-pod-lib-lint)
check_changes '^Firestore'
;;
@@ -68,6 +68,7 @@ fi
# Always rebuild if Travis configuration and/or build scripts changed.
check_changes '^.travis.yml'
+check_changes '^Gemfile.lock'
check_changes '^scripts/(build|if_changed).sh'
if [[ "$run" == true ]]; then
diff --git a/scripts/if_cron.sh b/scripts/if_cron.sh
new file mode 100755
index 0000000..c13a374
--- /dev/null
+++ b/scripts/if_cron.sh
@@ -0,0 +1,25 @@
+# Copyright 2018 Google
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Within Travis, check if running in a cron job.
+#
+# Examines the following Travis-supplied environment variables:
+# - TRAVIS_EVENT_TYPE - to check if this is a cron job
+#
+
+if [[ "$TRAVIS_EVENT_TYPE" == "cron" ]]; then
+ "$@"
+else
+ echo "skipped $*"
+fi
diff --git a/scripts/install_prereqs.sh b/scripts/install_prereqs.sh
index c86663f..c943369 100755
--- a/scripts/install_prereqs.sh
+++ b/scripts/install_prereqs.sh
@@ -19,28 +19,44 @@
# - PROJECT - Firebase or Firestore
# - METHOD - xcodebuild or cmake; default is xcodebuild
-if [[ -z "$METHOD" ]]; then
- METHOD="xcodebuild"
-fi
+bundle install
-case "$PROJECT-$METHOD" in
- *-xcodebuild)
- bundle install
+case "$PROJECT-$PLATFORM-$METHOD" in
+ Firebase-iOS-xcodebuild)
gem install xcpretty
+ bundle exec pod install --project-directory=Example --repo-update
+ bundle exec pod install --project-directory=Functions/Example
;;
- Firestore-cmake)
- bundle install
+ Firebase-*-xcodebuild)
+ gem install xcpretty
+ bundle exec pod install --project-directory=Example --repo-update
+ ;;
+
+ Firestore-*-xcodebuild)
+ gem install xcpretty
+ bundle exec pod install --project-directory=Firestore/Example --repo-update
+ ;;
+
+ *-pod-lib-lint)
+ bundle exec pod repo update
+ ;;
+
+ Firestore-*-cmake)
# xcpretty is helpful for the intermediate step which builds FirebaseCore
# using xcodebuild.
gem install xcpretty
brew outdated cmake || brew upgrade cmake
brew outdated go || brew upgrade go # Somehow the build for Abseil requires this.
+ bundle exec pod install --project-directory=Example --repo-update
+ bundle exec pod install --project-directory=Firestore/Example \
+ --no-repo-update
;;
*)
- echo "Unknown project-method combo" 1>&2
+ echo "Unknown project-platform-method combo" 1>&2
echo " PROJECT=$PROJECT" 1>&2
+ echo " PLATFORM=$PLATFORM" 1>&2
echo " METHOD=$METHOD" 1>&2
exit 1
;;
diff --git a/scripts/lint.sh b/scripts/lint.sh
index 9e33c87..d474129 100755
--- a/scripts/lint.sh
+++ b/scripts/lint.sh
@@ -60,7 +60,7 @@ objc_lint_options=(
if [[ $# -gt 0 ]]; then
# Interpret any command-line argument as a revision range
- command=(git diff --name-only)
+ command=(git diff --name-only --diff-filter=ACMR)
git_options+=("$@")
else
diff --git a/scripts/pod_install.sh b/scripts/pod_install.sh
deleted file mode 100755
index ff5ec96..0000000
--- a/scripts/pod_install.sh
+++ /dev/null
@@ -1,54 +0,0 @@
-# Copyright 2018 Google
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Within Travis, installs prerequisites for a build.
-
-# Examines the following configured environment variables that should be
-# specified in an env: block
-# - PROJECT - Firebase or Firestore
-# - METHOD - xcodebuild or cmake; default is xcodebuild
-# - PLATFORM - iOS, macOS, or tvOS
-
-if [[ -z "$METHOD" ]]; then
- METHOD="xcodebuild"
-fi
-
-case "$PROJECT-$METHOD-$PLATFORM" in
- Firebase-xcodebuild-iOS)
- bundle exec pod install --project-directory=Example --repo-update
- bundle exec pod install --project-directory=Functions/Example
- ;;
-
- Firebase-xcodebuild-*)
- bundle exec pod install --project-directory=Example --repo-update
- ;;
-
- Firestore-xcodebuild-*)
- bundle exec pod install --project-directory=Firestore/Example --repo-update
- ;;
-
- Firestore-cmake-*)
- bundle exec pod install --project-directory=Example --repo-update
- bundle exec pod install --project-directory=Firestore/Example \
- --no-repo-update
- ;;
-
- *)
- echo "Unknown project-method combo" 1>&2
- echo " PROJECT=$PROJECT" 1>&2
- echo " METHOD=$METHOD" 1>&2
- exit 1
- ;;
-esac
-