aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore
diff options
context:
space:
mode:
Diffstat (limited to 'Firestore')
-rw-r--r--Firestore/CHANGELOG.md2
-rw-r--r--Firestore/Example/Firestore.xcodeproj/project.pbxproj171
-rw-r--r--Firestore/Example/Podfile57
-rw-r--r--Firestore/Example/Tests/Core/FSTQueryListenerTests.mm24
-rw-r--r--Firestore/Example/Tests/Core/FSTViewTests.mm61
-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/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/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/FSTHelpers.h8
-rw-r--r--Firestore/Example/Tests/Util/FSTHelpers.mm40
-rw-r--r--Firestore/Source/API/FIRDocumentReference.mm20
-rw-r--r--Firestore/Source/API/FIRFieldPath.mm3
-rw-r--r--Firestore/Source/API/FIRFirestoreVersion.h2
-rw-r--r--Firestore/Source/API/FIRFirestoreVersion.mm2
-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.mm5
-rw-r--r--Firestore/Source/Core/FSTQuery.mm5
-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.mm31
-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.mm38
-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.mm30
-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.mm24
-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/FSTDatastore.mm2
-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/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.h3
-rw-r--r--Firestore/core/src/firebase/firestore/remote/serializer.cc230
-rw-r--r--Firestore/core/src/firebase/firestore/remote/serializer.h57
-rw-r--r--Firestore/core/src/firebase/firestore/util/CMakeLists.txt59
-rw-r--r--Firestore/core/src/firebase/firestore/util/async_queue.cc140
-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.cc296
-rw-r--r--Firestore/core/src/firebase/firestore/util/executor_libdispatch.h92
-rw-r--r--Firestore/core/src/firebase/firestore/util/executor_std.cc155
-rw-r--r--Firestore/core/src/firebase/firestore/util/executor_std.h281
-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/document_key_test.cc23
-rw-r--r--Firestore/core/test/firebase/firestore/remote/serializer_test.cc269
-rw-r--r--Firestore/core/test/firebase/firestore/util/CMakeLists.txt55
-rw-r--r--Firestore/core/test/firebase/firestore/util/async_queue_test.cc184
-rw-r--r--Firestore/core/test/firebase/firestore/util/async_queue_test.h47
-rw-r--r--Firestore/core/test/firebase/firestore/util/async_queue_test_libdispatch.cc86
-rw-r--r--Firestore/core/test/firebase/firestore/util/async_queue_test_std.cc41
-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.cc43
-rw-r--r--Firestore/core/test/firebase/firestore/util/executor_std_test.cc240
-rw-r--r--Firestore/core/test/firebase/firestore/util/executor_test.cc110
-rw-r--r--Firestore/core/test/firebase/firestore/util/executor_test.h46
-rw-r--r--Firestore/core/test/firebase/firestore/util/hashing_test.cc105
121 files changed, 5084 insertions, 1119 deletions
diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md
index 5543325..c97aa23 100644
--- a/Firestore/CHANGELOG.md
+++ b/Firestore/CHANGELOG.md
@@ -24,6 +24,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.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj
index 8aecc9f..6738e74 100644
--- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj
+++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj
@@ -27,7 +27,9 @@
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 */; };
+ 3DE7ABABD726C80991971BE1 /* Pods_Firestore_SwiftTests_iOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 44B1394B81D5FCA818943A06 /* Pods_Firestore_SwiftTests_iOS.framework */; };
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 */; };
54740A571FC914BA00713A1A /* secure_random_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A531FC913E500713A1A /* secure_random_test.cc */; };
@@ -155,18 +157,21 @@
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 */; };
+ B6FB467D208E9D3C00554BA2 /* async_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB467B208E9A8200554BA2 /* async_queue_test.cc */; };
+ B6FB4684208EA0EC00554BA2 /* async_queue_test_libdispatch.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4680208EA0BE00554BA2 /* async_queue_test_libdispatch.cc */; };
+ B6FB4685208EA0F000554BA2 /* async_queue_test_std.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4681208EA0BE00554BA2 /* async_queue_test_std.cc */; };
+ B6FB468E208F9BAB00554BA2 /* executor_libdispatch_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4689208F9B9100554BA2 /* executor_libdispatch_test.cc */; };
+ 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 */; };
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 */; };
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 */; };
@@ -237,12 +242,15 @@
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; };
+ 39F1102E452A53A1F93AAA1F /* Pods-Firestore_SwiftTests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_SwiftTests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_SwiftTests_iOS/Pods-Firestore_SwiftTests_iOS.release.xcconfig"; sourceTree = "<group>"; };
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>"; };
+ 44B1394B81D5FCA818943A06 /* Pods_Firestore_SwiftTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_SwiftTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
+ 54511E8D209805F8005BD28F /* hashing_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = hashing_test.cc; path = ../../core/test/firebase/firestore/util/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>"; };
@@ -365,6 +373,7 @@
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>"; };
+ 635C1D9B5E36BC4C12A35E70 /* Pods-Firestore_SwiftTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_SwiftTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_SwiftTests_iOS/Pods-Firestore_SwiftTests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
69F6A10DBD6187489481CD76 /* Pods_Firestore_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
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>"; };
@@ -401,6 +410,15 @@
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>"; };
+ B6FB467A208E9A8200554BA2 /* async_queue_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = async_queue_test.h; path = ../../core/test/firebase/firestore/util/async_queue_test.h; sourceTree = "<group>"; };
+ B6FB467B208E9A8200554BA2 /* async_queue_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = async_queue_test.cc; path = ../../core/test/firebase/firestore/util/async_queue_test.cc; sourceTree = "<group>"; };
+ B6FB4680208EA0BE00554BA2 /* async_queue_test_libdispatch.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = async_queue_test_libdispatch.cc; path = ../../core/test/firebase/firestore/util/async_queue_test_libdispatch.cc; sourceTree = "<group>"; };
+ B6FB4681208EA0BE00554BA2 /* async_queue_test_std.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = async_queue_test_std.cc; path = ../../core/test/firebase/firestore/util/async_queue_test_std.cc; sourceTree = "<group>"; };
+ B6FB4686208F9B9100554BA2 /* async_tests_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = async_tests_util.h; path = ../../core/test/firebase/firestore/util/async_tests_util.h; sourceTree = "<group>"; };
+ B6FB4687208F9B9100554BA2 /* executor_std_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = executor_std_test.cc; path = ../../core/test/firebase/firestore/util/executor_std_test.cc; sourceTree = "<group>"; };
+ B6FB4688208F9B9100554BA2 /* executor_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = executor_test.cc; path = ../../core/test/firebase/firestore/util/executor_test.cc; sourceTree = "<group>"; };
+ B6FB4689208F9B9100554BA2 /* executor_libdispatch_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = executor_libdispatch_test.cc; path = ../../core/test/firebase/firestore/util/executor_libdispatch_test.cc; sourceTree = "<group>"; };
+ B6FB468A208F9B9100554BA2 /* executor_test.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = executor_test.h; path = ../../core/test/firebase/firestore/util/executor_test.h; 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>"; };
@@ -431,7 +449,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- D9AF2279747DE7213156646C /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework in Frameworks */,
+ 3DE7ABABD726C80991971BE1 /* Pods_Firestore_SwiftTests_iOS.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -464,8 +482,6 @@
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 */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -493,10 +509,20 @@
54740A561FC913EB00713A1A /* util */ = {
isa = PBXGroup;
children = (
+ B6FB4686208F9B9100554BA2 /* async_tests_util.h */,
+ B6FB4689208F9B9100554BA2 /* executor_libdispatch_test.cc */,
+ B6FB4687208F9B9100554BA2 /* executor_std_test.cc */,
+ B6FB4688208F9B9100554BA2 /* executor_test.cc */,
+ B6FB468A208F9B9100554BA2 /* executor_test.h */,
+ B6FB4680208EA0BE00554BA2 /* async_queue_test_libdispatch.cc */,
+ B6FB4681208EA0BE00554BA2 /* async_queue_test_std.cc */,
+ B6FB467B208E9A8200554BA2 /* async_queue_test.cc */,
+ B6FB467A208E9A8200554BA2 /* async_queue_test.h */,
548DB926200D590300E00ABC /* assert_test.cc */,
54740A521FC913E500713A1A /* autoid_test.cc */,
AB380D01201BC69F00D97691 /* bits_test.cc */,
548DB928200D59F600E00ABC /* comparison_test.cc */,
+ 54511E8D209805F8005BD28F /* hashing_test.cc */,
54C2294E1FECABAE007D065B /* log_test.cc */,
AB380D03201BC6E400D97691 /* ordered_code_test.cc */,
54740A531FC913E500713A1A /* secure_random_test.cc */,
@@ -600,6 +626,7 @@
0E5F50EF80014608B1868944 /* Pods_Firestore_Example_iOS_Firestore_SwiftTests_iOS.framework */,
8525646842C83F703237BAA4 /* Pods_Firestore_Tests_iOS.framework */,
B4EEE10E8E59CC91309335CA /* Pods_Firestore_IntegrationTests_iOS.framework */,
+ 44B1394B81D5FCA818943A06 /* Pods_Firestore_SwiftTests_iOS.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -688,6 +715,8 @@
5A3E3BE5F322D66EE3D6CB65 /* Pods-Firestore_Tests_iOS.release.xcconfig */,
3C7CE22C50805C4A854C73A1 /* Pods-Firestore_IntegrationTests_iOS.debug.xcconfig */,
618AC3C38A174084B9420162 /* Pods-Firestore_IntegrationTests_iOS.release.xcconfig */,
+ 635C1D9B5E36BC4C12A35E70 /* Pods-Firestore_SwiftTests_iOS.debug.xcconfig */,
+ 39F1102E452A53A1F93AAA1F /* Pods-Firestore_SwiftTests_iOS.release.xcconfig */,
);
name = Pods;
sourceTree = "<group>";
@@ -958,7 +987,6 @@
6003F586195388D20070C39A /* Sources */,
6003F587195388D20070C39A /* Frameworks */,
6003F588195388D20070C39A /* Resources */,
- 7C5123A9C345ECE100DA21BD /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -997,7 +1025,7 @@
DE03B2981F2149D600A30B9C /* Sources */,
DE03B2D31F2149D600A30B9C /* Frameworks */,
DE03B2D81F2149D600A30B9C /* Resources */,
- DE03B2E41F2149D600A30B9C /* [CP] Embed Pods Frameworks */,
+ A677B831B09F9BD04DC6DF32 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@@ -1189,50 +1217,32 @@
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS-checkManifestLockResult.txt",
+ "$(DERIVED_FILE_DIR)/Pods-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;
};
- 7C5123A9C345ECE100DA21BD /* [CP] Embed Pods Frameworks */ = {
+ 8D94B6319191CD7344A4D1B9 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_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_Tests_iOS-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
- shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_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;
};
- 8D94B6319191CD7344A4D1B9 /* [CP] Check Pods Manifest.lock */ = {
+ 8F34C5E63ACEBD784CF82A45 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@@ -1243,38 +1253,56 @@
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-Firestore_Tests_iOS-checkManifestLockResult.txt",
+ "$(DERIVED_FILE_DIR)/Pods-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;
};
- 8F34C5E63ACEBD784CF82A45 /* [CP] Check Pods Manifest.lock */ = {
+ 9E2D564AC55ADE2D52B7E951 /* [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_SwiftTests_iOS/Pods-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] Check Pods Manifest.lock";
+ name = "[CP] Embed Pods Frameworks";
outputPaths = (
- "$(DERIVED_FILE_DIR)/Pods-SwiftBuildTest-checkManifestLockResult.txt",
+ "${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 = "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_SwiftTests_iOS/Pods-Firestore_SwiftTests_iOS-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
- 9E2D564AC55ADE2D52B7E951 /* [CP] Embed Pods Frameworks */ = {
+ A677B831B09F9BD04DC6DF32 /* [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",
+ "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/BoringSSL/openssl.framework",
"${BUILT_PRODUCTS_DIR}/GTMSessionFetcher/GTMSessionFetcher.framework",
"${BUILT_PRODUCTS_DIR}/GoogleToolboxForMac/GoogleToolboxForMac.framework",
@@ -1301,7 +1329,7 @@
);
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 = "\"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
BB3FE78ABF533BFC38839A0E /* [CP] Embed Pods Frameworks */ = {
@@ -1311,13 +1339,31 @@
);
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_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",
"${BUILT_PRODUCTS_DIR}/GoogleTest/GoogleTest.framework",
"${BUILT_PRODUCTS_DIR}/OCMock/OCMock.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",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GoogleTest.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
);
@@ -1344,24 +1390,6 @@
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 */ = {
- isa = PBXShellScriptBuildPhase;
- buildActionMask = 2147483647;
- files = (
- );
- inputPaths = (
- "${SRCROOT}/Pods/Target Support Files/Pods-Firestore_IntegrationTests_iOS/Pods-Firestore_IntegrationTests_iOS-frameworks.sh",
- "${BUILT_PRODUCTS_DIR}/OCMock/OCMock.framework",
- );
- name = "[CP] Embed Pods Frameworks";
- outputPaths = (
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OCMock.framework",
- );
- runOnlyForDeploymentPostprocessing = 0;
- shellPath = /bin/sh;
- 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 */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
@@ -1429,12 +1457,15 @@
DE2EF0861F3D0B6E003D0CDC /* FSTImmutableSortedDictionary+Testing.m in Sources */,
B686F2AF2023DDEE0028D6BE /* field_path_test.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.cc in Sources */,
5467FB01203E5717009C9584 /* FIRFirestoreTests.mm in Sources */,
+ B6FB4684208EA0EC00554BA2 /* async_queue_test_libdispatch.cc in Sources */,
5492E0A12021552D00B64F25 /* FSTMemoryLocalStoreTests.mm in Sources */,
5436F32420008FAD006E51E3 /* string_printf_test.cc in Sources */,
5492E067202154B900B64F25 /* FSTEventManagerTests.mm in Sources */,
@@ -1458,6 +1489,7 @@
5492E09D2021552D00B64F25 /* FSTLocalStoreTests.mm in Sources */,
5492E0A32021552D00B64F25 /* FSTLocalSerializerTests.mm in Sources */,
5492E0A72021552D00B64F25 /* FSTLevelDBKeyTests.mm in Sources */,
+ B6FB4685208EA0F000554BA2 /* async_queue_test_std.cc in Sources */,
5492E0A22021552D00B64F25 /* FSTQueryCacheTests.mm in Sources */,
5492E0A52021552D00B64F25 /* FSTMemoryRemoteDocumentCacheTests.mm in Sources */,
AB6B908820322E8800CC290A /* no_document_test.cc in Sources */,
@@ -1467,6 +1499,7 @@
5492E0C82021557E00B64F25 /* FSTDatastoreTests.mm in Sources */,
54995F6F205B6E12004EFFA0 /* leveldb_key_test.cc in Sources */,
5492E065202154B900B64F25 /* FSTViewTests.mm in Sources */,
+ B6FB467D208E9D3C00554BA2 /* async_queue_test.cc in Sources */,
5492E03C2021401F00B64F25 /* XCTestCase+Await.mm in Sources */,
B6152AD7202A53CB000E5744 /* document_key_test.cc in Sources */,
5467FB08203E6A44009C9584 /* app_testing.mm in Sources */,
@@ -1481,6 +1514,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 */,
@@ -1493,6 +1527,7 @@
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 */,
@@ -1605,7 +1640,7 @@
/* Begin XCBuildConfiguration section */
54C9EDF82040E16300A969CD /* Debug */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 9837221D251B8D40E7D7B454 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */;
+ baseConfigurationReference = 635C1D9B5E36BC4C12A35E70 /* Pods-Firestore_SwiftTests_iOS.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -1644,7 +1679,7 @@
};
54C9EDF92040E16300A969CD /* Release */ = {
isa = XCBuildConfiguration;
- baseConfigurationReference = 80025E2E892B94823962D11D /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.release.xcconfig */;
+ baseConfigurationReference = 39F1102E452A53A1F93AAA1F /* Pods-Firestore_SwiftTests_iOS.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CLANG_ANALYZER_NONNULL = YES;
@@ -1891,10 +1926,6 @@
OTHER_LDFLAGS = (
"$(inherited)",
"-l\"c++\"",
- "-framework",
- "\"OCMock\"",
- "-framework",
- "\"leveldb\"",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1932,10 +1963,6 @@
OTHER_LDFLAGS = (
"$(inherited)",
"-l\"c++\"",
- "-framework",
- "\"OCMock\"",
- "-framework",
- "\"leveldb\"",
);
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
PRODUCT_NAME = "$(TARGET_NAME)";
diff --git a/Firestore/Example/Podfile b/Firestore/Example/Podfile
index 1de1906..c1e02c8 100644
--- a/Firestore/Example/Podfile
+++ b/Firestore/Example/Podfile
@@ -17,25 +17,56 @@ pod 'FirebaseFirestore', :path => '../../'
target 'Firestore_Example_iOS' do
platform :ios, '8.0'
- target 'Firestore_Tests_iOS' do
- inherit! :search_paths
+ # Test targets are below to avoid problems with duplicate symbols when
+ # building as a static library (see comments below).
+end
- pod 'leveldb-library'
- pod 'OCMock'
- pod 'GoogleTest', :podspec => 'Tests/GoogleTest/GoogleTest.podspec'
- end
+target 'Firestore_Tests_iOS' do
+ platform :ios, '8.0'
- target 'Firestore_IntegrationTests_iOS' do
- inherit! :search_paths
+ pod 'leveldb-library'
+ pod 'OCMock'
+ pod 'GoogleTest', :podspec => 'Tests/GoogleTest/GoogleTest.podspec'
+end
- pod 'OCMock'
- end
+target 'Firestore_IntegrationTests_iOS' do
+ platform :ios, '8.0'
+end
- target 'Firestore_SwiftTests_iOS' do
- pod 'FirebaseFirestoreSwift', :path => '../../'
- end
+target 'Firestore_SwiftTests_iOS' do
+ platform :ios, '8.0'
+ pod 'FirebaseFirestoreSwift', :path => '../../'
end
target 'SwiftBuildTest' do
platform :ios, '8.0'
end
+
+# Firestore includes both Objective-C and C++ code, and the Firestore tests
+# consist of both XCTest-based tests in Objective-C and GoogleTest-based tests
+# in C++. The C++ tests must resolve the classes under test at link time, so
+# CocoaPods usual strategy linking Frameworks to the app and then resolving
+# those classes through run-time loading does not work in all cases.
+#
+# If use_frameworks! is disabled above, the project will encounter a ton of
+# duplicate Objective-C class warnings during test runs. Some of the tests will
+# fail too because duplicate classes also get duplicate static data and this
+# violates the expectations of code we depend upon.
+#
+# The workaround is to strip duplicate dependencies out of the example app,
+# which does not need them since it doesn't do anything other than act as a
+# host to the tests. This is based on the workaround posted here:
+#
+# https://github.com/CocoaPods/CocoaPods/issues/7155
+#
+# TODO(wilhuff): Reevaluate if this is needed once we require CocoaPods 1.5.1
+# which may address this.
+pre_install do |installer|
+ test_target = installer.aggregate_targets.find do |target|
+ target.name == 'Pods-Firestore_Tests_iOS'
+ end
+ app_target = installer.aggregate_targets.find do |target|
+ target.name == 'Pods-Firestore_Example_iOS'
+ end
+ app_target.pod_targets = app_target.pod_targets - test_target.pod_targets
+end
diff --git a/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
index 0454152..7ae9704 100644
--- a/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
+++ b/Firestore/Example/Tests/Core/FSTQueryListenerTests.mm
@@ -27,6 +27,8 @@
#import "Firestore/Example/Tests/Util/FSTHelpers.h"
+using firebase::firestore::model::DocumentKeySet;
+
NS_ASSUME_NONNULL_BEGIN
@interface FSTQueryListenerTests : XCTestCase
@@ -53,7 +55,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 +109,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 =
@@ -136,7 +138,7 @@ NS_ASSUME_NONNULL_BEGIN
[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);
@@ -178,7 +180,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 +220,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 +267,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 +300,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 +333,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 +376,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 +417,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 +443,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/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/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/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/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/Source/API/FIRDocumentReference.mm b/Firestore/Source/API/FIRDocumentReference.mm
index c2fc546..da67a5b 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];
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/FIRFirestoreVersion.h b/Firestore/Source/API/FIRFirestoreVersion.h
index 6fb21eb..aaf7976 100644
--- a/Firestore/Source/API/FIRFirestoreVersion.h
+++ b/Firestore/Source/API/FIRFirestoreVersion.h
@@ -19,4 +19,4 @@
#import <Foundation/Foundation.h>
/** Version string for the Firebase Firestore SDK. */
-FOUNDATION_EXPORT const unsigned char *const FirebaseFirestoreVersionString;
+FOUNDATION_EXPORT const unsigned char *const FIRFirestoreVersionString;
diff --git a/Firestore/Source/API/FIRFirestoreVersion.mm b/Firestore/Source/API/FIRFirestoreVersion.mm
index 8ebe814..8f0428c 100644
--- a/Firestore/Source/API/FIRFirestoreVersion.mm
+++ b/Firestore/Source/API/FIRFirestoreVersion.mm
@@ -27,5 +27,5 @@
#define STR(x) STR_EXPAND(x)
#define STR_EXPAND(x) #x
-extern "C" const unsigned char *const FirebaseFirestoreVersionString =
+extern "C" const unsigned char *const FIRFirestoreVersionString =
(const unsigned char *const)STR(FIRFirestore_VERSION);
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.mm b/Firestore/Source/Core/FSTFirestoreClient.mm
index 4f1a20b..658cf57 100644
--- a/Firestore/Source/Core/FSTFirestoreClient.mm
+++ b/Firestore/Source/Core/FSTFirestoreClient.mm
@@ -56,6 +56,7 @@ 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;
NS_ASSUME_NONNULL_BEGIN
@@ -311,11 +312,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,
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/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..8fa1278 100644
--- a/Firestore/Source/Local/FSTLocalSerializer.mm
+++ b/Firestore/Source/Local/FSTLocalSerializer.mm
@@ -30,9 +30,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 +103,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 +122,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 +132,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 +151,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 +188,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 +213,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..5087dfc 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];
}
@@ -78,18 +89,19 @@ NS_ASSUME_NONNULL_BEGIN
- (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..8ebc9ab 100644
--- a/Firestore/Source/Model/FSTDocument.mm
+++ b/Firestore/Source/Model/FSTDocument.mm
@@ -18,7 +18,6 @@
#include <utility>
-#import "Firestore/Source/Core/FSTSnapshotVersion.h"
#import "Firestore/Source/Model/FSTFieldValue.h"
#import "Firestore/Source/Util/FSTAssert.h"
@@ -29,26 +28,28 @@
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 {
+- (instancetype)initWithKey:(DocumentKey)key version:(SnapshotVersion)version {
FSTAssert(!!version, @"Version must not be nil.");
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;
@@ -110,8 +115,9 @@ NS_ASSUME_NONNULL_BEGIN
}
- (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 {
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..fdf6014 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];
}
@@ -556,7 +564,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/FSTDatastore.mm b/Firestore/Source/Remote/FSTDatastore.mm
index c7ee30f..f0852fe 100644
--- a/Firestore/Source/Remote/FSTDatastore.mm
+++ b/Firestore/Source/Remote/FSTDatastore.mm
@@ -185,7 +185,7 @@ typedef GRPCProtoCall * (^RPCFactory)(void);
// version as a macro, so it would be hardcoded based on version we have at compile time of
// the Firestore library, rather than the version available at runtime/at compile time by the
// user of the library.
- return [NSString stringWithFormat:@"gl-objc/ fire/%s grpc/", FirebaseFirestoreVersionString];
+ return [NSString stringWithFormat:@"gl-objc/ fire/%s grpc/", FIRFirestoreVersionString];
}
/** Returns the string to be used as google-cloud-resource-prefix header value. */
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..782e54c 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:(version ? version.value() : nil)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/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..7ce0985 100644
--- a/Firestore/core/src/firebase/firestore/model/snapshot_version.h
+++ b/Firestore/core/src/firebase/firestore/model/snapshot_version.h
@@ -44,6 +44,9 @@ class SnapshotVersion {
static const SnapshotVersion& None();
#if defined(__OBJC__)
+ SnapshotVersion() {
+ }
+
SnapshotVersion(FSTSnapshotVersion* version) // NOLINT(runtime/explicit)
: timestamp_{version.timestamp.seconds, version.timestamp.nanoseconds} {
}
diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.cc b/Firestore/core/src/firebase/firestore/remote/serializer.cc
index b5a0720..e81ea2d 100644
--- a/Firestore/core/src/firebase/firestore/remote/serializer.cc
+++ b/Firestore/core/src/firebase/firestore/remote/serializer.cc
@@ -25,15 +25,20 @@
#include <utility>
#include "Firestore/Protos/nanopb/google/firestore/v1beta1/document.pb.h"
+#include "Firestore/core/src/firebase/firestore/model/resource_path.h"
#include "Firestore/core/src/firebase/firestore/util/firebase_assert.h"
namespace firebase {
namespace firestore {
namespace remote {
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::FieldValue;
using firebase::firestore::model::ObjectValue;
+using firebase::firestore::model::ResourcePath;
using firebase::firestore::util::Status;
+using firebase::firestore::util::StatusOr;
namespace {
@@ -43,7 +48,7 @@ class Reader;
void EncodeObject(Writer* writer, const ObjectValue& object_value);
-ObjectValue DecodeObject(Reader* reader);
+ObjectValue::Map DecodeObject(Reader* reader);
/**
* Represents a nanopb tag.
@@ -196,6 +201,14 @@ class Reader {
return stream_.bytes_left;
}
+ Status status() const {
+ return status_;
+ }
+
+ void set_status(Status status) {
+ status_ = status;
+ }
+
private:
/**
* Creates a new Reader, based on the given nanopb pb_istream_t. Note that
@@ -220,6 +233,8 @@ class Reader {
*/
uint64_t ReadVarint();
+ Status status_ = Status::OK();
+
pb_istream_t stream_;
};
@@ -270,12 +285,17 @@ void Writer::WriteTag(Tag tag) {
Tag Reader::ReadTag() {
Tag tag;
+ if (!status_.ok()) return 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();
+ 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;
}
@@ -301,10 +321,11 @@ void Writer::WriteVarint(uint64_t value) {
* @return The decoded varint as a uint64_t.
*/
uint64_t Reader::ReadVarint() {
- uint64_t varint_value;
+ if (!status_.ok()) return 0;
+
+ uint64_t varint_value = 0;
if (!pb_decode_varint(&stream_, &varint_value)) {
- // TODO(rsgowman): figure out error handling
- abort();
+ status_ = Status(FirestoreErrorCode::DataLoss, PB_GET_ERROR(&stream_));
}
return varint_value;
}
@@ -315,9 +336,11 @@ void Writer::WriteNull() {
void Reader::ReadNull() {
uint64_t varint = ReadVarint();
+ if (!status_.ok()) return;
+
if (varint != google_protobuf_NullValue_NULL_VALUE) {
- // TODO(rsgowman): figure out error handling
- abort();
+ status_ = Status(FirestoreErrorCode::DataLoss,
+ "Input proto bytes cannot be parsed (invalid null value)");
}
}
@@ -327,14 +350,18 @@ void Writer::WriteBool(bool bool_value) {
bool Reader::ReadBool() {
uint64_t varint = ReadVarint();
+ if (!status_.ok()) return false;
+
switch (varint) {
case 0:
return false;
case 1:
return true;
default:
- // TODO(rsgowman): figure out error handling
- abort();
+ status_ =
+ Status(FirestoreErrorCode::DataLoss,
+ "Input proto bytes cannot be parsed (invalid bool value)");
+ return false;
}
}
@@ -357,17 +384,21 @@ void Writer::WriteString(const std::string& string_value) {
}
std::string Reader::ReadString() {
+ if (!status_.ok()) return "";
+
pb_istream_t substream;
if (!pb_make_string_substream(&stream_, &substream)) {
- // TODO(rsgowman): figure out error handling
- abort();
+ 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)) {
- // TODO(rsgowman): figure out error handling
- abort();
+ 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
@@ -375,10 +406,9 @@ std::string Reader::ReadString() {
// 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();
- }
+ FIREBASE_ASSERT_MESSAGE(
+ substream.bytes_left == 0,
+ "Bytes remaining in substream after supposedly reading all of them.");
pb_close_string_substream(&stream_, &substream);
@@ -431,29 +461,52 @@ void EncodeFieldValueImpl(Writer* writer, const FieldValue& field_value) {
FieldValue DecodeFieldValueImpl(Reader* reader) {
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_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();
@@ -465,12 +518,15 @@ FieldValue DecodeFieldValueImpl(Reader* reader) {
case google_firestore_v1beta1_Value_string_value_tag:
return FieldValue::StringValue(reader->ReadString());
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.");
}
}
@@ -533,24 +589,34 @@ 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 T();
+
pb_istream_t raw_substream;
if (!pb_make_string_substream(&stream_, &raw_substream)) {
- // TODO(rsgowman): figure out error handling
- abort();
+ status_ = Status(FirestoreErrorCode::DataLoss, PB_GET_ERROR(&stream_));
+ pb_close_string_substream(&stream_, &raw_substream);
+ return T();
}
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.
- if (substream.bytes_left() != 0) {
- // TODO(rsgowman): figure out error handling
- abort();
- }
+ 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;
@@ -591,6 +657,7 @@ void EncodeFieldsEntry(Writer* writer, const ObjectValue::Map::value_type& kv) {
ObjectValue::Map::value_type DecodeFieldsEntry(Reader* reader) {
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 +667,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,7 +675,7 @@ 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) {
@@ -622,12 +690,17 @@ void EncodeObject(Writer* writer, const ObjectValue& object_value) {
});
}
-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 +713,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 +721,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 +790,28 @@ 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)};
}
} // namespace remote
diff --git a/Firestore/core/src/firebase/firestore/remote/serializer.h b/Firestore/core/src/firebase/firestore/remote/serializer.h
index 7f08f7d..86aa6e2 100644
--- a/Firestore/core/src/firebase/firestore/remote/serializer.h
+++ b/Firestore/core/src/firebase/firestore/remote/serializer.h
@@ -19,12 +19,17 @@
#include <cstdint>
#include <cstdlib>
+#include <string>
#include <vector>
+#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/field_value.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,20 +50,13 @@ 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.
@@ -66,8 +64,9 @@ class Serializer {
* @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 +79,40 @@ 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;
+
private:
- // TODO(rsgowman): We don't need the database_id_ yet (but will eventually).
- // model::DatabaseId* database_id_;
+ const firebase::firestore::model::DatabaseId& database_id_;
};
} // namespace remote
diff --git a/Firestore/core/src/firebase/firestore/util/CMakeLists.txt b/Firestore/core/src/firebase/firestore/util/CMakeLists.txt
index 3afead1..29d91c7 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.cc
+ 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..71f5cc5
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/async_queue.cc
@@ -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.
+ */
+
+#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;
+}
+
+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(),
+ "Enforcing sequential order failed: currently executing operations "
+ "cannot enqueue more operations "
+ "(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.cc b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.cc
new file mode 100644
index 0000000..b40f0dd
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.cc
@@ -0,0 +1,296 @@
+/*
+ * 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{""};
+}
+
+void RunSynchronized(const ExecutorLibdispatch* const executor,
+ std::function<void()>&& work) {
+ if (executor->IsCurrentExecutor()) {
+ work();
+ } else {
+ DispatchSync(executor->dispatch_queue(), std::move(work));
+ }
+}
+
+} // 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, Executor::Operation work) {
+ // 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<const Executor::Operation*>(raw_work);
+ (*unwrap)();
+ });
+}
+
+// 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::system_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::system_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} {
+}
+ExecutorLibdispatch::ExecutorLibdispatch()
+ : ExecutorLibdispatch{dispatch_queue_create("com.google.firebase.firestore",
+ DISPATCH_QUEUE_SERIAL)} {
+}
+
+ExecutorLibdispatch::~ExecutorLibdispatch() {
+ // Turn any operations that might still be in the queue into no-ops, lest
+ // they try to access `ExecutorLibdispatch` after it gets destroyed. Because
+ // the queue is serial, by the time libdispatch gets to the newly-enqueued
+ // work, the pending operations that might have been in progress would have
+ // already finished.
+ RunSynchronized(this, [this] {
+ for (auto slot : schedule_) {
+ slot->MarkDone();
+ }
+ });
+}
+
+bool ExecutorLibdispatch::IsCurrentExecutor() const {
+ return GetCurrentQueueLabel().data() == GetTargetQueueLabel().data();
+}
+std::string ExecutorLibdispatch::CurrentExecutorName() const {
+ return GetCurrentQueueLabel().data();
+}
+std::string ExecutorLibdispatch::Name() const {
+ return GetTargetQueueLabel().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);
+ }
+ });
+}
+
+// GetLabel functions are guaranteed to never return a "null" string_view
+// (i.e. data() != nullptr).
+absl::string_view ExecutorLibdispatch::GetCurrentQueueLabel() const {
+ // 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));
+}
+
+absl::string_view ExecutorLibdispatch::GetTargetQueueLabel() const {
+ return StringViewFromDispatchLabel(
+ dispatch_queue_get_label(dispatch_queue()));
+}
+
+// Test-only methods
+
+bool ExecutorLibdispatch::IsScheduled(const Tag tag) const {
+ bool result = false;
+ RunSynchronized(this, [this, tag, &result] {
+ result = std::find_if(schedule_.begin(), schedule_.end(),
+ [&tag](const TimeSlot* const operation) {
+ return *operation == tag;
+ }) != schedule_.end();
+ });
+ 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_libdispatch.h b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.h
new file mode 100644
index 0000000..b32dbff
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/executor_libdispatch.h
@@ -0,0 +1,92 @@
+/*
+ * 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 <atomic>
+#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"
+
+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(const dispatch_queue_t queue, std::function<void()>&& work);
+
+// Similar to `DispatchAsync` but wraps `dispatch_sync_f`.
+void DispatchSync(const 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:
+ ExecutorLibdispatch();
+ explicit ExecutorLibdispatch(dispatch_queue_t dispatch_queue);
+ ~ExecutorLibdispatch();
+
+ 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:
+ // GetLabel functions are guaranteed to never return a "null" string_view
+ // (i.e. data() != nullptr).
+ absl::string_view GetCurrentQueueLabel() const;
+ absl::string_view GetTargetQueueLabel() const;
+
+ std::atomic<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_std.cc b/Firestore/core/src/firebase/firestore/util/executor_std.cc
new file mode 100644
index 0000000..59197e1
--- /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::system_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..4ac62e1
--- /dev/null
+++ b/Firestore/core/src/firebase/firestore/util/executor_std.h
@@ -0,0 +1,281 @@
+/*
+ * 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::system_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;
+
+ private:
+ 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/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..1dded05 100644
--- a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
+++ b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
@@ -33,18 +33,30 @@
#include <vector>
#include "Firestore/Protos/cpp/google/firestore/v1beta1/document.pb.h"
+#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
#include "Firestore/core/src/firebase/firestore/model/field_value.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::firestore::FirestoreErrorCode;
+using firebase::firestore::model::DatabaseId;
using firebase::firestore::model::FieldValue;
using firebase::firestore::model::ObjectValue;
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 +68,10 @@ TEST(Serializer, CanLinkToNanopb) {
// Fixture for running serializer tests.
class SerializerTest : public ::testing::Test {
public:
- SerializerTest() : serializer(/*DatabaseId("p", "d")*/) {
+ SerializerTest() : serializer(kDatabaseId) {
}
+
+ const DatabaseId kDatabaseId{"p", "d"};
Serializer serializer;
void ExpectRoundTrip(const FieldValue& model,
@@ -74,22 +88,69 @@ class SerializerTest : public ::testing::Test {
ExpectDeserializationRoundTrip(model, proto, type);
}
+ /**
+ * 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(FieldValue::BooleanValue(b), &bytes);
- EXPECT_TRUE(status.ok());
+ Status status = serializer->EncodeFieldValue(fv, &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 +158,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 +171,8 @@ 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);
@@ -128,9 +185,7 @@ 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);
@@ -145,28 +200,28 @@ 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.
-
-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 +236,7 @@ TEST_F(SerializerTest, WritesIntegers) {
}
}
-TEST_F(SerializerTest, WritesString) {
+TEST_F(SerializerTest, EncodesString) {
std::vector<std::string> cases{
"",
"a",
@@ -204,7 +259,7 @@ TEST_F(SerializerTest, WritesString) {
}
}
-TEST_F(SerializerTest, WritesEmptyMap) {
+TEST_F(SerializerTest, EncodesEmptyMap) {
FieldValue model = FieldValue::ObjectValueFromMap({});
google::firestore::v1beta1::Value proto;
@@ -213,7 +268,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 +313,157 @@ 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, 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));
+ }
+}
+
// 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..2e1e2f9 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.cc
+ 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_test_std.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_test_libdispatch.cc
+ 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_test.cc b/Firestore/core/test/firebase/firestore/util/async_queue_test.cc
new file mode 100644
index 0000000..bcee2e3
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/async_queue_test.cc
@@ -0,0 +1,184 @@
+/*
+ * 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()); });
+}
+
+TEST_P(AsyncQueueTest, CanScheduleOperationsInTheFuture) {
+ std::string steps;
+
+ queue.Enqueue([&steps] { steps += '1'; });
+ queue.Enqueue([&] {
+ queue.EnqueueAfterDelay(AsyncQueue::Milliseconds(5), kTimerId1, [&] {
+ steps += '4';
+ signal_finished();
+ });
+ queue.EnqueueAfterDelay(AsyncQueue::Milliseconds(1), 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_queue_test_libdispatch.cc b/Firestore/core/test/firebase/firestore/util/async_queue_test_libdispatch.cc
new file mode 100644
index 0000000..b4b9c63
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/async_queue_test_libdispatch.cc
@@ -0,0 +1,86 @@
+/*
+ * 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_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,
+ VerifyIsCurrentQueueRequiresBeingCalledAsync) {
+ 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_test_std.cc b/Firestore/core/test/firebase/firestore/util/async_queue_test_std.cc
new file mode 100644
index 0000000..9e69ad0
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/async_queue_test_std.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_tests_util.h b/Firestore/core/test/firebase/firestore/util/async_tests_util.h
new file mode 100644
index 0000000..422745b
--- /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::system_clock,
+ std::chrono::milliseconds>
+now() {
+ return std::chrono::time_point_cast<std::chrono::milliseconds>(
+ std::chrono::system_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.cc b/Firestore/core/test/firebase/firestore/util/executor_libdispatch_test.cc
new file mode 100644
index 0000000..0167c83
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/executor_libdispatch_test.cc
@@ -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.
+ */
+
+#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>();
+}
+
+} // namespace
+
+INSTANTIATE_TEST_CASE_P(ExecutorTestLibdispatch,
+ ExecutorTest,
+ ::testing::Values(ExecutorFactory));
+
+} // 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..43cad60
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/executor_std_test.cc
@@ -0,0 +1,240 @@
+/*
+ * 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));
+
+ EXPECT_FALSE(schedule.PopIfDue().has_value());
+ 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);
+ });
+
+ while (schedule.empty()) {
+ std::this_thread::sleep_for(chr::milliseconds(1));
+ }
+ const auto maybe_removed =
+ schedule.RemoveIf([](const int v) { return v == 2; });
+ 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..5cf389b
--- /dev/null
+++ b/Firestore/core/test/firebase/firestore/util/executor_test.cc
@@ -0,0 +1,110 @@
+/*
+ * 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, 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);
+}
+
+TEST_P(ExecutorTest, CanScheduleOperationsInTheFuture) {
+ std::string steps;
+
+ executor->Execute([&steps] { steps += '1'; });
+ Schedule(executor.get(), Executor::Milliseconds(5), [&] {
+ steps += '4';
+ signal_finished();
+ });
+ Schedule(executor.get(), Executor::Milliseconds(1),
+ [&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());
+}
+
+} // 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