diff --git a/FirebaseFirestore.podspec b/FirebaseFirestore.podspec index 50d0f1689dc..319d488e30b 100644 --- a/FirebaseFirestore.podspec +++ b/FirebaseFirestore.podspec @@ -90,6 +90,7 @@ Google Cloud Firestore is a NoSQL document database built for automatic scaling, abseil_version = '0.20200225.0' s.dependency 'abseil/algorithm', abseil_version s.dependency 'abseil/base', abseil_version + s.dependency 'abseil/container/flat_hash_map', abseil_version s.dependency 'abseil/memory', abseil_version s.dependency 'abseil/meta', abseil_version s.dependency 'abseil/strings/strings', abseil_version diff --git a/Firestore/CHANGELOG.md b/Firestore/CHANGELOG.md index 7cc68e33311..7213d21e298 100644 --- a/Firestore/CHANGELOG.md +++ b/Firestore/CHANGELOG.md @@ -1,3 +1,6 @@ +# Unreleased +- [changed] Internal refactor to improve serialization performance. + # v8.4.0 - [fixed] Fixed handling of Unicode characters in log and assertion messages (#8372). diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 4d5c5631bfc..eec0bacd1dc 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -8,12 +8,14 @@ /* Begin PBXBuildFile section */ 000212BFBE7A17712FC9754A /* leveldb_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B629525F7A1AAC1AB765C74F /* leveldb_lru_garbage_collector_test.cc */; }; + 002EC02E9F86464049A69A06 /* nanopb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */; }; 0087625FD31D76E1365C589E /* string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0EE5300F8233D14025EF0456 /* string_apple_test.mm */; }; 009CDC5D8C96F54A229F462F /* local_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F8043813A5D16963EC02B182 /* local_serializer_test.cc */; }; 009CDC6F03AC92F3E345085E /* collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129C1F315EE100DD57A1 /* collection_spec_test.json */; }; 009F5174BD172716AFE9F20A /* string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0EE5300F8233D14025EF0456 /* string_apple_test.mm */; }; 00B7AFE2A7C158DD685EB5EE /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; }; 00F1CB487E8E0DA48F2E8FEC /* message_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CE37875365497FFA8687B745 /* message_test.cc */; }; + 0131DEDEF2C3CCAB2AB918A5 /* nanopb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */; }; 01C66732ECCB83AB1D896026 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; 01D9704C3AAA13FAD2F962AB /* statusor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352D20A3B3D7003E0143 /* statusor_test.cc */; }; 020AFD89BB40E5175838BB76 /* local_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F8043813A5D16963EC02B182 /* local_serializer_test.cc */; }; @@ -28,7 +30,6 @@ 048A55EED3241ABC28752F86 /* memory_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 74FBEFA4FE4B12C435011763 /* memory_mutation_queue_test.cc */; }; 04D7D9DB95E66FECF2C0A412 /* bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F7FC06E0A47D393DE1759AE1 /* bundle_cache_test.cc */; }; 0500A324CEC854C5B0CF364C /* FIRCollectionReferenceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E045202154AA00B64F25 /* FIRCollectionReferenceTests.mm */; }; - 051D3E20184AF195266EF678 /* no_document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908720322E8800CC290A /* no_document_test.cc */; }; 0535C1B65DADAE1CE47FA3CA /* string_format_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */; }; 056542AD1D0F78E29E22EFA9 /* grpc_connection_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D9649021544D4F00EB9CFB /* grpc_connection_test.cc */; }; 0575F3004B896D94456A74CE /* status_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3CAA33F964042646FDDAF9F9 /* status_testing.cc */; }; @@ -37,6 +38,7 @@ 06A3926F89C847846BE4D6BE /* http.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */; }; 06BCEB9C65DFAA142F3D3F0B /* view_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = A5466E7809AD2871FFDE6C76 /* view_testing.cc */; }; 072D805A94E767DE4D371881 /* FSTSyncEngineTestDriver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */; }; + 0794FACCB1C0C4881A76C28D /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; }; 079E63E270F3EFCA175D2705 /* cc_compilation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1B342370EAE3AA02393E33EB /* cc_compilation_test.cc */; }; 07A64E6C4EB700E3AF3FD496 /* document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908320322E4D00CC290A /* document_test.cc */; }; 07ADEF17BFBC07C0C2E306F6 /* FSTMockDatastore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02D20213FFC00B64F25 /* FSTMockDatastore.mm */; }; @@ -54,12 +56,10 @@ 0A4E1B5E3E853763AE6ED7AE /* grpc_stream_tester.cc in Sources */ = {isa = PBXBuildFile; fileRef = 87553338E42B8ECA05BA987E /* grpc_stream_tester.cc */; }; 0A52B47C43B7602EE64F53A7 /* cc_compilation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1B342370EAE3AA02393E33EB /* cc_compilation_test.cc */; }; 0A6FBE65A7FE048BAD562A15 /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; }; - 0A800CA749750B01E36A6787 /* field_value_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6D0EE49C1D5AF75664D0EBE4 /* field_value_benchmark.cc */; }; 0ABCE06A0D96EA3899B3A259 /* query_engine_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B8A853940305237AFDA8050B /* query_engine_test.cc */; }; 0AE084A7886BC11B8C305122 /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CFC201A2EE200D97691 /* string_util_test.cc */; }; 0B002E2E2012B32EB801C6D5 /* bundle_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 79EAA9F7B1B9592B5F053923 /* bundle_spec_test.json */; }; 0B071E9044CEEF666D829354 /* field_filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E8551D6C6FB0B1BACE9E5BAD /* field_filter_test.cc */; }; - 0B4D2668C1E81DF6D62BA9BF /* field_value_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6D0EE49C1D5AF75664D0EBE4 /* field_value_benchmark.cc */; }; 0B55CD5CB8DFEBF2D22A2332 /* byte_stream_cpp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 01D10113ECC5B446DB35E96D /* byte_stream_cpp_test.cc */; }; 0B7B24194E2131F5C325FE0E /* async_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB467B208E9A8200554BA2 /* async_queue_test.cc */; }; 0B9BD73418289EFF91917934 /* bits_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D01201BC69F00D97691 /* bits_test.cc */; }; @@ -77,8 +77,8 @@ 0E4C94369FFF7EC0C9229752 /* iterator_adaptors_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0353420A3D8CB003E0143 /* iterator_adaptors_test.cc */; }; 0EA40EDACC28F445F9A3F32F /* pretty_printing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB323F9553050F4F6490F9FF /* pretty_printing_test.cc */; }; 0EDFC8A6593477E1D17CDD8F /* leveldb_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8E9CD82E60893DDD7757B798 /* leveldb_bundle_cache_test.cc */; }; - 0EF74A344612147DE4261A4B /* field_value_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6D0EE49C1D5AF75664D0EBE4 /* field_value_benchmark.cc */; }; 0F54634745BA07B09BDC14D7 /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; + 0F5D0C58444564D97AF0C98E /* nanopb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */; }; 0F99BB63CE5B3CFE35F9027E /* event_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F57521E161450FAF89075ED /* event_manager_test.cc */; }; 0FA4D5601BE9F0CB5EC2882C /* local_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F8043813A5D16963EC02B182 /* local_serializer_test.cc */; }; 0FBDD5991E8F6CD5F8542474 /* latlng.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9220B89AAC00B5BCE7 /* latlng.pb.cc */; }; @@ -88,6 +88,7 @@ 113190791F42202FDE1ABC14 /* FIRQuerySnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04F202154AA00B64F25 /* FIRQuerySnapshotTests.mm */; }; 1145D70555D8CDC75183A88C /* leveldb_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C7942B6244F4C416B11B86C /* leveldb_mutation_queue_test.cc */; }; 11BC867491A6631D37DE56A8 /* async_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 872C92ABD71B12784A1C5520 /* async_testing.cc */; }; + 11EBD28DBD24063332433947 /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; }; 11F8EE69182C9699E90A9E3D /* database_info_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D92E20235D22000A432D /* database_info_test.cc */; }; 12158DFCEE09D24B7988A340 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; }; 121F0FB9DCCBFB7573C7AF48 /* bundle_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B5C2A94EE24E60543F62CC35 /* bundle_serializer_test.cc */; }; @@ -143,13 +144,13 @@ 1D7919CD2A05C15803F5FE05 /* leveldb_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C7942B6244F4C416B11B86C /* leveldb_mutation_queue_test.cc */; }; 1DB3013C5FC736B519CD65A3 /* common.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D221C2DDC800EFB9CC /* common.pb.cc */; }; 1DCA68BB2EF7A9144B35411F /* leveldb_opener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */; }; - 1E1683C9F65658270745EDCD /* no_document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908720322E8800CC290A /* no_document_test.cc */; }; 1E2AE064CF32A604DC7BFD4D /* to_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B696858D2214B53900271095 /* to_string_test.cc */; }; 1E41BEEDB1F7F23D8A7C47E6 /* bundle_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6ECAF7DE28A19C69DF386D88 /* bundle_reader_test.cc */; }; 1E42CD0F60EB22A5D0C86D1F /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; 1E6E2AE74B7C9DEDFC07E76B /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; }; 1E8A00ABF414AC6C6591D9AC /* cc_compilation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1B342370EAE3AA02393E33EB /* cc_compilation_test.cc */; }; 1E8F5F37052AB0C087D69DF9 /* leveldb_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8E9CD82E60893DDD7757B798 /* leveldb_bundle_cache_test.cc */; }; + 1EE2B61B15AAA7C864188A59 /* object_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 214877F52A705012D6720CA0 /* object_value_test.cc */; }; 1F38FD2703C58DFA69101183 /* document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D821C2DDC800EFB9CC /* document.pb.cc */; }; 1F3DD2971C13CBBFA0D84866 /* memory_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 74FBEFA4FE4B12C435011763 /* memory_mutation_queue_test.cc */; }; 1F4930A8366F74288121F627 /* create_noop_connectivity_monitor.cc in Sources */ = {isa = PBXBuildFile; fileRef = CF39535F2C41AB0006FA6C0E /* create_noop_connectivity_monitor.cc */; }; @@ -212,17 +213,16 @@ 2CBA4FA327C48B97D31F6373 /* watch_change_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2D7472BC70C024D736FF74D9 /* watch_change_test.cc */; }; 2CD379584D1D35AAEA271D21 /* sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */; }; 2D220B9ABFA36CD7AC43D0A7 /* time_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5497CB76229DECDE000FB92F /* time_testing.cc */; }; - 2D3401180516B739494C7EFC /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; }; 2D65D31D71A75B046C47B0EB /* view_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = A5466E7809AD2871FFDE6C76 /* view_testing.cc */; }; 2DB56B6DED2C93014AE5C51A /* write_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A51F315EE100DD57A1 /* write_spec_test.json */; }; 2E0BBA7E627EB240BA11B0D0 /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; }; 2E169CF1E9E499F054BB873A /* FSTEventAccumulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */; }; 2E76BC76BBCE5FCDDCF5EEBE /* leveldb_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8E9CD82E60893DDD7757B798 /* leveldb_bundle_cache_test.cc */; }; 2EAD77559EC654E6CA4D3E21 /* FIRSnapshotMetadataTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04D202154AA00B64F25 /* FIRSnapshotMetadataTests.mm */; }; + 2EB2EE24076A4E4621E38E45 /* nanopb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */; }; 2EC1C4D202A01A632339A161 /* field_transform_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7515B47C92ABEEC66864B55C /* field_transform_test.cc */; }; 2F3740131CC8F8230351B91D /* byte_stream_cpp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 01D10113ECC5B446DB35E96D /* byte_stream_cpp_test.cc */; }; 2F6E23D7888FC82475C63010 /* token_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7DF2023A3EF00BA84F0 /* token_test.cc */; }; - 2F7D76FF225B550F83B95A72 /* FSTUserDataConverterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */; }; 2F8FDF35BBB549A6F4D2118E /* FSTMemorySpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02F20213FFC00B64F25 /* FSTMemorySpecTests.mm */; }; 2FA0BAE32D587DF2EA5EEB97 /* async_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB467B208E9A8200554BA2 /* async_queue_test.cc */; }; 3040FD156E1B7C92B0F2A70C /* ordered_code_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0473AFFF5567E667A125347B /* ordered_code_benchmark.cc */; }; @@ -287,6 +287,7 @@ 3CFFA6F016231446367E3A69 /* listen_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */; }; 3D22F56C0DE7C7256C75DC06 /* tree_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4D20A36DBB00BCEB75 /* tree_sorted_map_test.cc */; }; 3D9619906F09108E34FF0C95 /* FSTSmokeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */; }; + 3DBB48F077C97200F32B51A0 /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; }; 3DBBC644BE08B140BCC23BD5 /* string_apple_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */; }; 3DDC57212ADBA9AD498EAA4C /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; 3DF1AB74036BD8AEF4430FA6 /* firebase_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */; }; @@ -306,6 +307,7 @@ 41EAC526C543064B8F3F7EDA /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; }; 42063E6AE9ADF659AA6D4E18 /* FSTSmokeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07C202154EB00B64F25 /* FSTSmokeTests.mm */; }; 42208EDA18C500BC271B6E95 /* FSTSyncEngineTestDriver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */; }; + 432056C4D1259F76C80FC2A8 /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8D9892F204959C50613F16C8 /* FSTUserDataReaderTests.mm */; }; 433474A3416B76645FFD17BB /* hashing_test_apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = B69CF3F02227386500B281C8 /* hashing_test_apple.mm */; }; 43EDB01D1641D96C40DA1889 /* credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D9342023966E000A432D /* credentials_provider_test.cc */; }; 444298A613D027AC67F7E977 /* memory_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9765D47FA12FA283F4EFAD02 /* memory_lru_garbage_collector_test.cc */; }; @@ -387,6 +389,7 @@ 5412671C23D1536B001E41A0 /* remote_document_cache_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5412671A23D1536B001E41A0 /* remote_document_cache_benchmark.mm */; }; 5412671D23D153EB001E41A0 /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; }; 54131E9720ADE679001DF3FF /* string_format_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54131E9620ADE678001DF3FF /* string_format_test.cc */; }; + 5424AB6FF5035495C03344E7 /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8D9892F204959C50613F16C8 /* FSTUserDataReaderTests.mm */; }; 544129DA21C2DDC800EFB9CC /* common.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D221C2DDC800EFB9CC /* common.pb.cc */; }; 544129DB21C2DDC800EFB9CC /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; 544129DC21C2DDC800EFB9CC /* query.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D621C2DDC800EFB9CC /* query.pb.cc */; }; @@ -419,9 +422,6 @@ 547E9A4522F9EA7300A275E0 /* document_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 547E9A4122F9EA7300A275E0 /* document_set_test.cc */; }; 547E9A4622F9EA7300A275E0 /* document_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 547E9A4122F9EA7300A275E0 /* document_set_test.cc */; }; 547E9A4722F9EA7300A275E0 /* document_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 547E9A4122F9EA7300A275E0 /* document_set_test.cc */; }; - 548180A5228DEF1A004F70CD /* FSTUserDataConverterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */; }; - 548180A6228DEF1A004F70CD /* FSTUserDataConverterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */; }; - 548180A7228DEF1A004F70CD /* FSTUserDataConverterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */; }; 548DB929200D59F600E00ABC /* comparison_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB928200D59F600E00ABC /* comparison_test.cc */; }; 5491BC721FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; 5491BC731FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; @@ -649,6 +649,7 @@ 6F511ABFD023AEB81F92DB12 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; }; 6F914209F46E6552B5A79570 /* async_queue_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4681208EA0BE00554BA2 /* async_queue_std_test.cc */; }; 6FAC16B7FBD3B40D11A6A816 /* target.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */; }; + 6FC85C48CF8235BA1845E1C8 /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8D9892F204959C50613F16C8 /* FSTUserDataReaderTests.mm */; }; 6FD2369F24E884A9D767DD80 /* FIRDocumentSnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04B202154AA00B64F25 /* FIRDocumentSnapshotTests.mm */; }; 6FF2B680CC8631B06C7BD7AB /* FSTMemorySpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02F20213FFC00B64F25 /* FSTMemorySpecTests.mm */; }; 70A171FC43BE328767D1B243 /* path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 403DBF6EFB541DFD01582AA3 /* path_test.cc */; }; @@ -674,6 +675,7 @@ 743DF2DF38CE289F13F44043 /* status_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3CAA33F964042646FDDAF9F9 /* status_testing.cc */; }; 7495E3BAE536CD839EE20F31 /* FSTLevelDBSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02C20213FFB00B64F25 /* FSTLevelDBSpecTests.mm */; }; 74985DE2C7EF4150D7A455FD /* statusor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352D20A3B3D7003E0143 /* statusor_test.cc */; }; + 75A176239B37354588769206 /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8D9892F204959C50613F16C8 /* FSTUserDataReaderTests.mm */; }; 75D124966E727829A5F99249 /* FIRTypeTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E071202154D600B64F25 /* FIRTypeTests.mm */; }; 76A5447D76F060E996555109 /* task_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 899FC22684B0F7BEEAE13527 /* task_test.cc */; }; 7731E564468645A4A62E2A3C /* leveldb_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */; }; @@ -684,6 +686,7 @@ 784FCB02C76096DACCBA11F2 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; 795A0E11B3951ACEA2859C8A /* mutation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C8522DE226C467C54E6788D8 /* mutation_test.cc */; }; 79987AF2DF1FCE799008B846 /* CodableGeoPointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5495EB022040E90200EBA509 /* CodableGeoPointTests.swift */; }; + 799AE5C2A38FCB435B1AB7EC /* nanopb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */; }; 79D86DD18BB54D2D69DC457F /* leveldb_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0840319686A223CC4AD3FAB1 /* leveldb_remote_document_cache_test.cc */; }; 7A2D523AEF58B1413CC8D64F /* query_engine_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B8A853940305237AFDA8050B /* query_engine_test.cc */; }; 7A3BE0ED54933C234FDE23D1 /* leveldb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 332485C4DCC6BA0DBB5E31B7 /* leveldb_util_test.cc */; }; @@ -718,7 +721,6 @@ 804B0C6CCE3933CF3948F249 /* grpc_streaming_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D964922154AB8F00EB9CFB /* grpc_streaming_reader_test.cc */; }; 8077722A6BB175D3108CDC55 /* leveldb_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0840319686A223CC4AD3FAB1 /* leveldb_remote_document_cache_test.cc */; }; 80AB93C807F35539EEC510B2 /* leveldb_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B629525F7A1AAC1AB765C74F /* leveldb_lru_garbage_collector_test.cc */; }; - 8146D5979B2A0B63C79B7AC4 /* FSTUserDataConverterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */; }; 814724DE70EFC3DDF439CD78 /* executor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4688208F9B9100554BA2 /* executor_test.cc */; }; 816E8E62DC163649BA96951C /* EncodableFieldValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1235769122B7E915007DDFA9 /* EncodableFieldValueTests.swift */; }; 81A6B241E63540900F205817 /* view_snapshot_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CC572A9168BBEF7B83E4BBC5 /* view_snapshot_test.cc */; }; @@ -740,7 +742,6 @@ 856A1EAAD674ADBDAAEDAC37 /* bundle_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */; }; 85B8918FC8C5DC62482E39C3 /* resource_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2B02024FFD70028D6BE /* resource_path_test.cc */; }; 85BC2AB572A400114BF59255 /* limbo_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129E1F315EE100DD57A1 /* limbo_spec_test.json */; }; - 85D301119D7175F82E12892E /* field_value_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6D0EE49C1D5AF75664D0EBE4 /* field_value_benchmark.cc */; }; 85D61BDC7FB99B6E0DD3AFCA /* mutation.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */; }; 85D7C370C7812166A467FEE9 /* string_apple_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */; }; 86004E06C088743875C13115 /* load_bundle_task_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8F1A7B4158D9DD76EE4836BF /* load_bundle_task_test.cc */; }; @@ -752,6 +753,7 @@ 86E6FC2B7657C35B342E1436 /* sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */; }; 8705C4856498F66E471A0997 /* FIRWriteBatchTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06F202154D600B64F25 /* FIRWriteBatchTests.mm */; }; 873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; }; + 87B5972F1C67CB8D53ADA024 /* object_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 214877F52A705012D6720CA0 /* object_value_test.cc */; }; 87B5AC3EBF0E83166B142FA4 /* string_apple_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */; }; 881E55152AB34465412F8542 /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; 88929ED628DA8DD9592974ED /* task_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 899FC22684B0F7BEEAE13527 /* task_test.cc */; }; @@ -779,7 +781,6 @@ 9012B0E121B99B9C7E54160B /* query_engine_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B8A853940305237AFDA8050B /* query_engine_test.cc */; }; 9016EF298E41456060578C90 /* field_transform_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7515B47C92ABEEC66864B55C /* field_transform_test.cc */; }; 906DB5C85F57EFCBD2027E60 /* grpc_unary_call_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D964942163E63900EB9CFB /* grpc_unary_call_test.cc */; }; - 9073AFB51EA26A818C29131E /* no_document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908720322E8800CC290A /* no_document_test.cc */; }; 907DF0E63248DBF0912CC56D /* filesystem_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = BA02DA2FCD0001CFC6EB08DA /* filesystem_testing.cc */; }; 90B9302B082E6252AF4E7DC7 /* leveldb_migrations_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EF83ACD5E1E9F25845A9ACED /* leveldb_migrations_test.cc */; }; 90FE088B8FD9EC06EEED1F39 /* memory_index_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = DB5A1E760451189DA36028B3 /* memory_index_manager_test.cc */; }; @@ -807,6 +808,7 @@ 9617B75E9E27E7BA46D87EF3 /* query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B9C261C26C5D311E1E3C0CB9 /* query_test.cc */; }; 96552D8E218F68DDCFE210A0 /* status_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5493A423225F9990006DE7BA /* status_apple_test.mm */; }; 96898170B456EAF092F73BBC /* defer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8ABAC2E0402213D837F73DC3 /* defer_test.cc */; }; + 96E54377873FCECB687A459B /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; }; 974FF09E6AFD24D5A39B898B /* local_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F8043813A5D16963EC02B182 /* local_serializer_test.cc */; }; 97729B53698C0E52EB165003 /* field_filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E8551D6C6FB0B1BACE9E5BAD /* field_filter_test.cc */; }; 9774A6C2AA02A12D80B34C3C /* database_id_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB71064B201FA60300344F18 /* database_id_test.cc */; }; @@ -824,7 +826,6 @@ 9C366448F9BA7A4AC0821AF7 /* bundle_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 79EAA9F7B1B9592B5F053923 /* bundle_spec_test.json */; }; 9C86EEDEA131BFD50255EEF1 /* comparison_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 548DB928200D59F600E00ABC /* comparison_test.cc */; }; 9CE07BAAD3D3BC5F069D38FE /* grpc_streaming_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D964922154AB8F00EB9CFB /* grpc_streaming_reader_test.cc */; }; - 9D0E720F5A6DBD48FF325016 /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; }; 9D71628E38D9F64C965DF29E /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; 9E656F4FE92E8BFB7F625283 /* to_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B696858D2214B53900271095 /* to_string_test.cc */; }; 9EE1447AA8E68DF98D0590FF /* precondition_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5520A36E1F00BCEB75 /* precondition_test.cc */; }; @@ -835,6 +836,7 @@ A06FBB7367CDD496887B86F8 /* leveldb_opener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */; }; A0C6C658DFEE58314586907B /* offline_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A11F315EE100DD57A1 /* offline_spec_test.json */; }; A0E1C7F5C7093A498F65C5CF /* memory_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB4AB1388538CD3CB19EB028 /* memory_bundle_cache_test.cc */; }; + A124744C6CBEF3DD415A1A72 /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8D9892F204959C50613F16C8 /* FSTUserDataReaderTests.mm */; }; A1563EFEB021936D3FFE07E3 /* field_mask_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5320A36E1F00BCEB75 /* field_mask_test.cc */; }; A17DBC8F24127DA8A381F865 /* testutil.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352820A3B3BD003E0143 /* testutil.cc */; }; A192648233110B7B8BD65528 /* field_transform_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7515B47C92ABEEC66864B55C /* field_transform_test.cc */; }; @@ -874,14 +876,12 @@ AAC15E7CCAE79619B2ABB972 /* XCTestCase+Await.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0372021401E00B64F25 /* XCTestCase+Await.mm */; }; AAF2F02E77A80C9CDE2C0C7A /* filesystem_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51859B394D01C0C507282F1 /* filesystem_test.cc */; }; AB2BAB0BD77FF05CC26FCF75 /* async_queue_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4681208EA0BE00554BA2 /* async_queue_std_test.cc */; }; - AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; }; AB380CFB2019388600D97691 /* target_id_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CF82019382300D97691 /* target_id_generator_test.cc */; }; AB380CFE201A2F4500D97691 /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CFC201A2EE200D97691 /* string_util_test.cc */; }; AB380D02201BC69F00D97691 /* bits_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D01201BC69F00D97691 /* bits_test.cc */; }; AB380D04201BC6E400D97691 /* ordered_code_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D03201BC6E400D97691 /* ordered_code_test.cc */; }; AB38D93020236E21000A432D /* database_info_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D92E20235D22000A432D /* database_info_test.cc */; }; AB6B908420322E4D00CC290A /* document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908320322E4D00CC290A /* document_test.cc */; }; - AB6B908820322E8800CC290A /* no_document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908720322E8800CC290A /* no_document_test.cc */; }; AB6D588EB21A2C8D40CEB408 /* byte_stream_cpp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 01D10113ECC5B446DB35E96D /* byte_stream_cpp_test.cc */; }; AB7BAB342012B519001E0872 /* geo_point_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB7BAB332012B519001E0872 /* geo_point_test.cc */; }; AB8209455BAA17850D5E196D /* http.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */; }; @@ -917,6 +917,7 @@ AF81B6A91987826426F18647 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; }; AFAC87E03815769ABB11746F /* append_only_list_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5477CDE922EE71C8000FCC1E /* append_only_list_test.cc */; }; AFB0ACCF130713DF6495E110 /* writer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BC3C788D290A935C353CEAA1 /* writer_test.cc */; }; + AFB2455806D7C4100C16713B /* object_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 214877F52A705012D6720CA0 /* object_value_test.cc */; }; AFE84E7B0C356CD2A113E56E /* status_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3CAA33F964042646FDDAF9F9 /* status_testing.cc */; }; B03F286F3AEC3781C386C646 /* FIRNumericTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D5B25E7E7D6873CBA4571841 /* FIRNumericTransformTests.mm */; }; B0B779769926304268200015 /* query_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 731541602214AFFA0037F4DC /* query_spec_test.json */; }; @@ -970,6 +971,7 @@ B8062EBDB8E5B680E46A6DD1 /* geo_point_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB7BAB332012B519001E0872 /* geo_point_test.cc */; }; B83A1416C3922E2F3EBA77FE /* grpc_stream_tester.cc in Sources */ = {isa = PBXBuildFile; fileRef = 87553338E42B8ECA05BA987E /* grpc_stream_tester.cc */; }; B842780CF42361ACBBB381A9 /* autoid_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A521FC913E500713A1A /* autoid_test.cc */; }; + B844B264311E18051B1671ED /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; }; B896E5DE1CC27347FAC009C3 /* BasicCompileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0761F61F2FE68D003233AF /* BasicCompileTests.swift */; }; B921A4F35B58925D958DD9A6 /* reference_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 132E32997D781B896672D30A /* reference_set_test.cc */; }; B94A967AAB5C9ECC0CB06706 /* fake_credentials_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = DCC17AF218430D8BB28DD197 /* fake_credentials_provider.cc */; }; @@ -994,6 +996,7 @@ BCA720A0F54D23654F806323 /* ConditionalConformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3228F51DCDC2E90D5C58F97 /* ConditionalConformanceTests.swift */; }; BD6CC8614970A3D7D2CF0D49 /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; }; BDD2D1812BAD962E3C81A53F /* hashing_test_apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = B69CF3F02227386500B281C8 /* hashing_test_apple.mm */; }; + BDDAE67000DBF10E9EA7FED0 /* nanopb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */; }; BDF3A6C121F2773BB3A347A7 /* counting_query_engine.cc in Sources */ = {isa = PBXBuildFile; fileRef = 99434327614FEFF7F7DC88EC /* counting_query_engine.cc */; }; BE767D2312D2BE84484309A0 /* event_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F57521E161450FAF89075ED /* event_manager_test.cc */; }; BE92E16A9B9B7AD5EB072919 /* string_format_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */; }; @@ -1025,7 +1028,6 @@ C524026444E83EEBC1773650 /* objc_type_traits_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 2A0CF41BA5AED6049B0BEB2C /* objc_type_traits_apple_test.mm */; }; C5655568EC2A9F6B5E6F9141 /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; C57B15CADD8C3E806B154C19 /* task_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 899FC22684B0F7BEEAE13527 /* task_test.cc */; }; - C591407ABE1394B4042AB7CA /* field_value_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6D0EE49C1D5AF75664D0EBE4 /* field_value_benchmark.cc */; }; C5F1E2220E30ED5EAC9ABD9E /* mutation.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */; }; C663A8B74B57FD84717DEA21 /* delayed_constructor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D0A6E9136804A41CEC9D55D4 /* delayed_constructor_test.cc */; }; C6BF529243414C53DF5F1012 /* memory_local_store_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F6CA0C5638AB6627CB5B4CF4 /* memory_local_store_test.cc */; }; @@ -1075,7 +1077,6 @@ D43F7601F3F3DE3125346D42 /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D93220239654000A432D /* user_test.cc */; }; D4572060A0FD4D448470D329 /* leveldb_transaction_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 88CF09277CFA45EE1273E3BA /* leveldb_transaction_test.cc */; }; D4D8BA32ACC5C2B1B29711C0 /* memory_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9765D47FA12FA283F4EFAD02 /* memory_lru_garbage_collector_test.cc */; }; - D541EA6C61FBB8913BA5C3C3 /* field_value_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6D0EE49C1D5AF75664D0EBE4 /* field_value_benchmark.cc */; }; D550446303227FB1B381133C /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; D57F4CB3C92CE3D4DF329B78 /* serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 61F72C5520BC48FD001A68CB /* serializer_test.cc */; }; D59FAEE934987D4C4B2A67B2 /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; @@ -1088,6 +1089,7 @@ D69B97FF4C065EACEDD91886 /* FSTSyncEngineTestDriver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */; }; D6DE74259F5C0CCA010D6A0D /* grpc_stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6BBE42F21262CF400C6A53E /* grpc_stream_test.cc */; }; D6E0E54CD1640E726900828A /* document_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6152AD5202A5385000E5744 /* document_key_test.cc */; }; + D711B3F495923680B6FC2FC6 /* object_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 214877F52A705012D6720CA0 /* object_value_test.cc */; }; D73BBA4AB42940AB187169E3 /* listen_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A01F315EE100DD57A1 /* listen_spec_test.json */; }; D756A1A63E626572EE8DF592 /* firestore.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D421C2DDC800EFB9CC /* firestore.pb.cc */; }; D77941FD93DBE862AEF1F623 /* FSTTransactionTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E07B202154EB00B64F25 /* FSTTransactionTests.mm */; }; @@ -1100,7 +1102,6 @@ D9EF7FC0E3F8646B272B427E /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; DA1D665B12AA1062DCDEA6BD /* async_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB467B208E9A8200554BA2 /* async_queue_test.cc */; }; DA4303684707606318E1914D /* target_id_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CF82019382300D97691 /* target_id_generator_test.cc */; }; - DA9FA01D1A4D7EC7ACA14DAB /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; }; DABB9FB61B1733F985CBF713 /* executor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4688208F9B9100554BA2 /* executor_test.cc */; }; DAC43DD1FDFBAB1FE1AD6BE5 /* firebase_credentials_provider_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = ABC1D7E22023CDC500BA84F0 /* firebase_credentials_provider_test.mm */; }; DAFF0CF921E64AC30062958F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = DAFF0CF821E64AC30062958F /* AppDelegate.m */; }; @@ -1121,7 +1122,6 @@ DD213F68A6F79E1D4924BD95 /* Pods_Firestore_Example_macOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E42355285B9EF55ABD785792 /* Pods_Firestore_Example_macOS.framework */; }; DD5976A45071455FF3FE74B8 /* string_win_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 79507DF8378D3C42F5B36268 /* string_win_test.cc */; }; DD6C480629B3F87933FAF440 /* filesystem_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = BA02DA2FCD0001CFC6EB08DA /* filesystem_testing.cc */; }; - DDBC6DB41D1A43CFF01288A2 /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; }; DDD219222EEE13E3F9F2C703 /* leveldb_transaction_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 88CF09277CFA45EE1273E3BA /* leveldb_transaction_test.cc */; }; DDDE74C752E65DE7D39A7166 /* view_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = A5466E7809AD2871FFDE6C76 /* view_testing.cc */; }; DE03B2D41F2149D600A30B9C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; @@ -1135,6 +1135,7 @@ DEF4BF5FAA83C37100408F89 /* bundle_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 79EAA9F7B1B9592B5F053923 /* bundle_spec_test.json */; }; DF27137C8EA7D095D68851B4 /* field_filter_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E8551D6C6FB0B1BACE9E5BAD /* field_filter_test.cc */; }; DF4B3835C5AA4835C01CD255 /* local_store_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 307FF03D0297024D59348EBD /* local_store_test.cc */; }; + DF7ABEB48A650117CBEBCD26 /* object_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 214877F52A705012D6720CA0 /* object_value_test.cc */; }; E08297B35E12106105F448EB /* ordered_code_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0473AFFF5567E667A125347B /* ordered_code_benchmark.cc */; }; E084921EFB7CF8CB1E950D6C /* iterator_adaptors_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0353420A3D8CB003E0143 /* iterator_adaptors_test.cc */; }; E0E640226A1439C59BBBA9C1 /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; }; @@ -1154,7 +1155,6 @@ E435450184AEB51EE8435F66 /* write.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D921C2DDC800EFB9CC /* write.pb.cc */; }; E441A53D035479C53C74A0E6 /* recovery_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 9C1AFCC9E616EC33D6E169CF /* recovery_spec_test.json */; }; E4A573B7C9227C3C24661B5B /* ordered_code_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D03201BC6E400D97691 /* ordered_code_test.cc */; }; - E4EEF6AAFCD33303CE9E5408 /* field_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB356EF6200EA5EB0089B766 /* field_value_test.cc */; }; E500AB82DF2E7F3AFDB1AB3F /* to_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B696858D2214B53900271095 /* to_string_test.cc */; }; E50187548B537DBCDBF7F9F0 /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CFC201A2EE200D97691 /* string_util_test.cc */; }; E51957EDECF741E1D3C3968A /* writer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BC3C788D290A935C353CEAA1 /* writer_test.cc */; }; @@ -1173,8 +1173,6 @@ E8495A8D1E11C0844339CCA3 /* database_info_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D92E20235D22000A432D /* database_info_test.cc */; }; E884336B43BBD1194C17E3C4 /* status_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3CAA33F964042646FDDAF9F9 /* status_testing.cc */; }; E8BA7055EDB8B03CC99A528F /* recovery_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 9C1AFCC9E616EC33D6E169CF /* recovery_spec_test.json */; }; - E9430D3EBDAE12E9016B708F /* no_document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908720322E8800CC290A /* no_document_test.cc */; }; - E9B704651F9783B70F2D5E86 /* FSTUserDataConverterTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */; }; EA38690795FBAA182A9AA63E /* FIRDatabaseTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06C202154D500B64F25 /* FIRDatabaseTests.mm */; }; EA46611779C3EEF12822508C /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; }; EAA1962BFBA0EBFBA53B343F /* bundle_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */; }; @@ -1200,9 +1198,11 @@ EECC1EC64CA963A8376FA55C /* persistence_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9113B6F513D0473AEABBAF1F /* persistence_testing.cc */; }; EF3518F84255BAF3EBD317F6 /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; }; EF43FF491B9282E0330E4CA2 /* remote_event_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 584AE2C37A55B408541A6FF3 /* remote_event_test.cc */; }; + EF79998EBE4C72B97AB1880E /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; }; F05B277F16BDE6A47FE0F943 /* local_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F8043813A5D16963EC02B182 /* local_serializer_test.cc */; }; F08DA55D31E44CB5B9170CCE /* limbo_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129E1F315EE100DD57A1 /* limbo_spec_test.json */; }; F091532DEE529255FB008E25 /* snapshot_version_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */; }; + F0C8EB1F4FB56401CFA4F374 /* object_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 214877F52A705012D6720CA0 /* object_value_test.cc */; }; F10A3E4E164A5458DFF7EDE6 /* leveldb_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0840319686A223CC4AD3FAB1 /* leveldb_remote_document_cache_test.cc */; }; F19B749671F2552E964422F7 /* FIRListenerRegistrationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06B202154D500B64F25 /* FIRListenerRegistrationTests.mm */; }; F272A8C41D2353700A11D1FB /* field_mask_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5320A36E1F00BCEB75 /* field_mask_test.cc */; }; @@ -1217,6 +1217,7 @@ F56E9334642C207D7D85D428 /* pretty_printing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB323F9553050F4F6490F9FF /* pretty_printing_test.cc */; }; F58A23FEF328EB74F681FE83 /* index_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AE4A9E38D65688EE000EE2A1 /* index_manager_test.cc */; }; F5A654E92FF6F3FF16B93E6B /* mutation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C8522DE226C467C54E6788D8 /* mutation_test.cc */; }; + F5BDECEB3B43BD1591EEADBD /* FSTUserDataReaderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8D9892F204959C50613F16C8 /* FSTUserDataReaderTests.mm */; }; F6079BFC9460B190DA85C2E6 /* pretty_printing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB323F9553050F4F6490F9FF /* pretty_printing_test.cc */; }; F609600E9A88A4D44FD1FCEB /* FSTSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E03020213FFC00B64F25 /* FSTSpecTests.mm */; }; F660788F69B4336AC6CD2720 /* offline_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A11F315EE100DD57A1 /* offline_spec_test.json */; }; @@ -1249,7 +1250,6 @@ FD8EA96A604E837092ACA51D /* ordered_code_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D03201BC6E400D97691 /* ordered_code_test.cc */; }; FE1C0263F6570DAC54A60F5C /* FIRTimestampTest.m in Sources */ = {isa = PBXBuildFile; fileRef = B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */; }; FE701C2D739A5371BCBD62B9 /* leveldb_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C7942B6244F4C416B11B86C /* leveldb_mutation_queue_test.cc */; }; - FEF55ECFB0CA317B351179AB /* no_document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908720322E8800CC290A /* no_document_test.cc */; }; FF3405218188DFCE586FB26B /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; }; FF4FA5757D13A2B7CEE40F04 /* document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D821C2DDC800EFB9CC /* document.pb.cc */; }; FF6333B8BD9732C068157221 /* memory_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB4AB1388538CD3CB19EB028 /* memory_bundle_cache_test.cc */; }; @@ -1331,6 +1331,7 @@ 166CE73C03AB4366AAC5201C /* leveldb_index_manager_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_index_manager_test.cc; sourceTree = ""; }; 1B342370EAE3AA02393E33EB /* cc_compilation_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = cc_compilation_test.cc; path = api/cc_compilation_test.cc; sourceTree = ""; }; 1CA9800A53669EFBFFB824E3 /* memory_remote_document_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_remote_document_cache_test.cc; sourceTree = ""; }; + 214877F52A705012D6720CA0 /* object_value_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = object_value_test.cc; sourceTree = ""; }; 2220F583583EFC28DE792ABE /* Pods_Firestore_IntegrationTests_tvOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_tvOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2286F308EFB0534B1BDE05B9 /* memory_target_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_target_cache_test.cc; sourceTree = ""; }; 277EAACC4DD7C21332E8496A /* lru_garbage_collector_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = lru_garbage_collector_test.cc; sourceTree = ""; }; @@ -1357,6 +1358,7 @@ 3F0992A4B83C60841C52E960 /* Pods-Firestore_Example_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS/Pods-Firestore_Example_iOS.release.xcconfig"; sourceTree = ""; }; 3FBAA6F05C0B46A522E3B5A7 /* bundle_cache_test.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = bundle_cache_test.h; sourceTree = ""; }; 403DBF6EFB541DFD01582AA3 /* path_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = path_test.cc; sourceTree = ""; }; + 40F9D09063A07F710811A84F /* value_util_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = value_util_test.cc; sourceTree = ""; }; 432C71959255C5DBDF522F52 /* byte_stream_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = byte_stream_test.cc; sourceTree = ""; }; 4334F87873015E3763954578 /* status_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = status_testing.h; sourceTree = ""; }; 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = hard_assert_test.cc; sourceTree = ""; }; @@ -1388,7 +1390,6 @@ 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTGoogleTestTests.mm; sourceTree = ""; }; 5477CDE922EE71C8000FCC1E /* append_only_list_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = append_only_list_test.cc; sourceTree = ""; }; 547E9A4122F9EA7300A275E0 /* document_set_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = document_set_test.cc; sourceTree = ""; }; - 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTUserDataConverterTests.mm; sourceTree = ""; }; 548DB928200D59F600E00ABC /* comparison_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = comparison_test.cc; sourceTree = ""; }; 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTIntegrationTestCase.mm; sourceTree = ""; }; 5492E02C20213FFB00B64F25 /* FSTLevelDBSpecTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTLevelDBSpecTests.mm; sourceTree = ""; }; @@ -1512,7 +1513,6 @@ 64AA92CFA356A2360F3C5646 /* filesystem_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = filesystem_testing.h; sourceTree = ""; }; 69E6C311558EC77729A16CF1 /* Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS/Pods-Firestore_Example_iOS-Firestore_SwiftTests_iOS.debug.xcconfig"; sourceTree = ""; }; 6AE927CDFC7A72BF825BE4CB /* Pods-Firestore_Tests_tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_tvOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_tvOS/Pods-Firestore_Tests_tvOS.release.xcconfig"; sourceTree = ""; }; - 6D0EE49C1D5AF75664D0EBE4 /* field_value_benchmark.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = field_value_benchmark.cc; sourceTree = ""; }; 6E8302DE210222ED003E1EA3 /* FSTFuzzTestFieldPath.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FSTFuzzTestFieldPath.h; sourceTree = ""; }; 6E8302DF21022309003E1EA3 /* FSTFuzzTestFieldPath.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestFieldPath.mm; sourceTree = ""; }; 6EA39FDD20FE820E008D461F /* FSTFuzzTestSerializer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestSerializer.mm; sourceTree = ""; }; @@ -1523,6 +1523,7 @@ 6EDD3B5C20BF247500C33877 /* Firestore_FuzzTests_iOS-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Firestore_FuzzTests_iOS-Info.plist"; sourceTree = ""; }; 6EDD3B5E20BF24D000C33877 /* FSTFuzzTestsPrincipal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFuzzTestsPrincipal.mm; sourceTree = ""; }; 6F57521E161450FAF89075ED /* event_manager_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = event_manager_test.cc; sourceTree = ""; }; + 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = nanopb_util_test.cc; path = nanopb/nanopb_util_test.cc; sourceTree = ""; }; 71140E5D09C6E76F7C71B2FC /* fake_target_metadata_provider.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = fake_target_metadata_provider.cc; sourceTree = ""; }; 71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 731541602214AFFA0037F4DC /* query_spec_test.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = query_spec_test.json; sourceTree = ""; }; @@ -1552,6 +1553,7 @@ 8A41BBE832158C76BE901BC9 /* mutation_queue_test.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = mutation_queue_test.h; sourceTree = ""; }; 8ABAC2E0402213D837F73DC3 /* defer_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = defer_test.cc; sourceTree = ""; }; 8C058C8BE2723D9A53CCD64B /* persistence_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = persistence_testing.h; sourceTree = ""; }; + 8D9892F204959C50613F16C8 /* FSTUserDataReaderTests.mm */ = {isa = PBXFileReference; includeInIndex = 1; path = FSTUserDataReaderTests.mm; sourceTree = ""; }; 8E002F4AD5D9B6197C940847 /* Firestore.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = Firestore.podspec; path = ../Firestore.podspec; sourceTree = ""; }; 8E9CD82E60893DDD7757B798 /* leveldb_bundle_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = leveldb_bundle_cache_test.cc; sourceTree = ""; }; 8F1A7B4158D9DD76EE4836BF /* load_bundle_task_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = load_bundle_task_test.cc; path = api/load_bundle_task_test.cc; sourceTree = ""; }; @@ -1570,7 +1572,6 @@ A70E82DD627B162BEF92B8ED /* Pods-Firestore_Example_tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_tvOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Example_tvOS/Pods-Firestore_Example_tvOS.debug.xcconfig"; sourceTree = ""; }; A853C81A6A5A51C9D0389EDA /* bundle_loader_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = bundle_loader_test.cc; path = bundle/bundle_loader_test.cc; sourceTree = ""; }; AB323F9553050F4F6490F9FF /* pretty_printing_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = pretty_printing_test.cc; path = nanopb/pretty_printing_test.cc; sourceTree = ""; }; - AB356EF6200EA5EB0089B766 /* field_value_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = field_value_test.cc; sourceTree = ""; }; AB380CF82019382300D97691 /* target_id_generator_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = target_id_generator_test.cc; sourceTree = ""; }; AB380CFC201A2EE200D97691 /* string_util_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = string_util_test.cc; sourceTree = ""; }; AB380D01201BC69F00D97691 /* bits_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = bits_test.cc; sourceTree = ""; }; @@ -1581,7 +1582,6 @@ AB38D93620239689000A432D /* empty_credentials_provider_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = empty_credentials_provider_test.cc; sourceTree = ""; }; AB4AB1388538CD3CB19EB028 /* memory_bundle_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = memory_bundle_cache_test.cc; sourceTree = ""; }; AB6B908320322E4D00CC290A /* document_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = document_test.cc; sourceTree = ""; }; - AB6B908720322E8800CC290A /* no_document_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = no_document_test.cc; sourceTree = ""; }; AB71064B201FA60300344F18 /* database_id_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = database_id_test.cc; sourceTree = ""; }; AB7BAB332012B519001E0872 /* geo_point_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = geo_point_test.cc; sourceTree = ""; }; ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = snapshot_version_test.cc; sourceTree = ""; }; @@ -2055,6 +2055,7 @@ 5342CDDB137B4E93E2E85CCA /* byte_string_test.cc */, CE37875365497FFA8687B745 /* message_test.cc */, 2DAA26538D1A93A39F8AC373 /* nanopb_testing.h */, + 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */, AB323F9553050F4F6490F9FF /* pretty_printing_test.cc */, BC3C788D290A935C353CEAA1 /* writer_test.cc */, ); @@ -2331,14 +2332,13 @@ 549CCA5320A36E1F00BCEB75 /* field_mask_test.cc */, B686F2AD2023DDB20028D6BE /* field_path_test.cc */, 7515B47C92ABEEC66864B55C /* field_transform_test.cc */, - 6D0EE49C1D5AF75664D0EBE4 /* field_value_benchmark.cc */, - AB356EF6200EA5EB0089B766 /* field_value_test.cc */, C8522DE226C467C54E6788D8 /* mutation_test.cc */, - AB6B908720322E8800CC290A /* no_document_test.cc */, + 214877F52A705012D6720CA0 /* object_value_test.cc */, 549CCA5520A36E1F00BCEB75 /* precondition_test.cc */, B686F2B02024FFD70028D6BE /* resource_path_test.cc */, ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */, 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */, + 40F9D09063A07F710811A84F /* value_util_test.cc */, ); path = model; sourceTree = ""; @@ -2407,7 +2407,7 @@ B65D34A7203C99090076A5E1 /* FIRTimestampTest.m */, 5492E047202154AA00B64F25 /* FSTAPIHelpers.h */, 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */, - 548180A4228DEF1A004F70CD /* FSTUserDataConverterTests.mm */, + 8D9892F204959C50613F16C8 /* FSTUserDataReaderTests.mm */, ); path = API; sourceTree = ""; @@ -3434,7 +3434,7 @@ F3261CBFC169DB375A0D9492 /* FSTMockDatastore.mm in Sources */, A907244EE37BC32C8D82948E /* FSTSpecTests.mm in Sources */, 072D805A94E767DE4D371881 /* FSTSyncEngineTestDriver.mm in Sources */, - 548180A6228DEF1A004F70CD /* FSTUserDataConverterTests.mm in Sources */, + 5424AB6FF5035495C03344E7 /* FSTUserDataReaderTests.mm in Sources */, 6DCA8E54E652B78EFF3EEDAC /* XCTestCase+Await.mm in Sources */, 45939AFF906155EA27D281AB /* annotations.pb.cc in Sources */, FF3405218188DFCE586FB26B /* app_testing.mm in Sources */, @@ -3483,8 +3483,6 @@ 07B1E8C62772758BC82FEBEE /* field_mask_test.cc in Sources */, D9366A834BFF13246DC3AF9E /* field_path_test.cc in Sources */, C961FA581F87000DF674BBC8 /* field_transform_test.cc in Sources */, - C591407ABE1394B4042AB7CA /* field_value_benchmark.cc in Sources */, - 9D0E720F5A6DBD48FF325016 /* field_value_test.cc in Sources */, 60C72F86D2231B1B6592A5E6 /* filesystem_test.cc in Sources */, 907DF0E63248DBF0912CC56D /* filesystem_testing.cc in Sources */, A61AE3D94C975A87EFA82ADA /* firebase_credentials_provider_test.mm in Sources */, @@ -3531,8 +3529,9 @@ C5F1E2220E30ED5EAC9ABD9E /* mutation.pb.cc in Sources */, 0DBD29A16030CDCD55E38CAB /* mutation_queue_test.cc in Sources */, 1CC9BABDD52B2A1E37E2698D /* mutation_test.cc in Sources */, - 051D3E20184AF195266EF678 /* no_document_test.cc in Sources */, + BDDAE67000DBF10E9EA7FED0 /* nanopb_util_test.cc in Sources */, 16FE432587C1B40AF08613D2 /* objc_type_traits_apple_test.mm in Sources */, + 87B5972F1C67CB8D53ADA024 /* object_value_test.cc in Sources */, E08297B35E12106105F448EB /* ordered_code_benchmark.cc in Sources */, 72AD91671629697074F2545B /* ordered_code_test.cc in Sources */, DB7E9C5A59CCCDDB7F0C238A /* path_test.cc in Sources */, @@ -3580,6 +3579,7 @@ 5D51D8B166D24EFEF73D85A2 /* transform_operation_test.cc in Sources */, 5F19F66D8B01BA2B97579017 /* tree_sorted_map_test.cc in Sources */, 16F52ECC6FA8A0587CD779EB /* user_test.cc in Sources */, + 11EBD28DBD24063332433947 /* value_util_test.cc in Sources */, A9A9994FB8042838671E8506 /* view_snapshot_test.cc in Sources */, AD8F0393B276B2934D251AAC /* view_test.cc in Sources */, 2D65D31D71A75B046C47B0EB /* view_testing.cc in Sources */, @@ -3624,7 +3624,7 @@ 31D8E3D925FA3F70AA20ACCE /* FSTMockDatastore.mm in Sources */, D5E9954FC1C5ABBC7A180B33 /* FSTSpecTests.mm in Sources */, D69B97FF4C065EACEDD91886 /* FSTSyncEngineTestDriver.mm in Sources */, - 548180A7228DEF1A004F70CD /* FSTUserDataConverterTests.mm in Sources */, + A124744C6CBEF3DD415A1A72 /* FSTUserDataReaderTests.mm in Sources */, AAC15E7CCAE79619B2ABB972 /* XCTestCase+Await.mm in Sources */, 1C19D796DB6715368407387A /* annotations.pb.cc in Sources */, 6EEA00A737690EF82A3C91C6 /* app_testing.mm in Sources */, @@ -3673,8 +3673,6 @@ ED4E2AC80CAF2A8FDDAC3DEE /* field_mask_test.cc in Sources */, 41EAC526C543064B8F3F7EDA /* field_path_test.cc in Sources */, A192648233110B7B8BD65528 /* field_transform_test.cc in Sources */, - 0EF74A344612147DE4261A4B /* field_value_benchmark.cc in Sources */, - E4EEF6AAFCD33303CE9E5408 /* field_value_test.cc in Sources */, AAF2F02E77A80C9CDE2C0C7A /* filesystem_test.cc in Sources */, C4C7A8D11DC394EF81B7B1FA /* filesystem_testing.cc in Sources */, DAC43DD1FDFBAB1FE1AD6BE5 /* firebase_credentials_provider_test.mm in Sources */, @@ -3721,8 +3719,9 @@ 153F3E4E9E3A0174E29550B4 /* mutation.pb.cc in Sources */, 94BBB23B93E449D03FA34F87 /* mutation_queue_test.cc in Sources */, 5E6F9184B271F6D5312412FF /* mutation_test.cc in Sources */, - FEF55ECFB0CA317B351179AB /* no_document_test.cc in Sources */, + 0131DEDEF2C3CCAB2AB918A5 /* nanopb_util_test.cc in Sources */, 9AC28D928902C6767A11F5FC /* objc_type_traits_apple_test.mm in Sources */, + F0C8EB1F4FB56401CFA4F374 /* object_value_test.cc in Sources */, B3C87C635527A2E57944B789 /* ordered_code_benchmark.cc in Sources */, FD8EA96A604E837092ACA51D /* ordered_code_test.cc in Sources */, 0963F6D7B0F9AE1E24B82866 /* path_test.cc in Sources */, @@ -3770,6 +3769,7 @@ 5EE21E86159A1911E9503BC1 /* transform_operation_test.cc in Sources */, 627253FDEC6BB5549FE77F4E /* tree_sorted_map_test.cc in Sources */, 596C782EFB68131380F8EEF8 /* user_test.cc in Sources */, + 0794FACCB1C0C4881A76C28D /* value_util_test.cc in Sources */, 1B4794A51F4266556CD0976B /* view_snapshot_test.cc in Sources */, C1F196EC5A7C112D2F7C7724 /* view_test.cc in Sources */, 3451DC1712D7BF5D288339A2 /* view_testing.cc in Sources */, @@ -3826,7 +3826,7 @@ BFEAC4151D3AA8CE1F92CC2D /* FSTSpecTests.mm in Sources */, F2AB7EACA1B9B1A7046D3995 /* FSTSyncEngineTestDriver.mm in Sources */, D77941FD93DBE862AEF1F623 /* FSTTransactionTests.mm in Sources */, - E9B704651F9783B70F2D5E86 /* FSTUserDataConverterTests.mm in Sources */, + 432056C4D1259F76C80FC2A8 /* FSTUserDataReaderTests.mm in Sources */, 3B1E27D951407FD237E64D07 /* FirestoreEncoderTests.swift in Sources */, 4D42E5C756229C08560DD731 /* XCTestCase+Await.mm in Sources */, 276A563D546698B6AAC20164 /* annotations.pb.cc in Sources */, @@ -3876,8 +3876,6 @@ F272A8C41D2353700A11D1FB /* field_mask_test.cc in Sources */, AF6D6C47F9A25C65BFDCBBA0 /* field_path_test.cc in Sources */, B667366CB06893DFF472902E /* field_transform_test.cc in Sources */, - 0B4D2668C1E81DF6D62BA9BF /* field_value_benchmark.cc in Sources */, - DDBC6DB41D1A43CFF01288A2 /* field_value_test.cc in Sources */, D6486C7FFA8BE6F9C7D2F4C4 /* filesystem_test.cc in Sources */, C3E4EE9615367213A71FEECF /* filesystem_testing.cc in Sources */, 3DF1AB74036BD8AEF4430FA6 /* firebase_credentials_provider_test.mm in Sources */, @@ -3924,8 +3922,9 @@ BBDFE0000C4D7E529E296ED4 /* mutation.pb.cc in Sources */, C8A573895D819A92BF16B5E5 /* mutation_queue_test.cc in Sources */, F5A654E92FF6F3FF16B93E6B /* mutation_test.cc in Sources */, - 1E1683C9F65658270745EDCD /* no_document_test.cc in Sources */, + 0F5D0C58444564D97AF0C98E /* nanopb_util_test.cc in Sources */, C524026444E83EEBC1773650 /* objc_type_traits_apple_test.mm in Sources */, + AFB2455806D7C4100C16713B /* object_value_test.cc in Sources */, 28691225046DF9DF181B3350 /* ordered_code_benchmark.cc in Sources */, E4A573B7C9227C3C24661B5B /* ordered_code_test.cc in Sources */, 70A171FC43BE328767D1B243 /* path_test.cc in Sources */, @@ -3973,6 +3972,7 @@ 15BF63DFF3A7E9A5376C4233 /* transform_operation_test.cc in Sources */, 54B91B921DA757C64CC67C90 /* tree_sorted_map_test.cc in Sources */, 8D5A9E6E43B6F47431841FE2 /* user_test.cc in Sources */, + 96E54377873FCECB687A459B /* value_util_test.cc in Sources */, 3A307F319553A977258BB3D6 /* view_snapshot_test.cc in Sources */, 89C71AEAA5316836BB1D5A01 /* view_test.cc in Sources */, 06BCEB9C65DFAA142F3D3F0B /* view_testing.cc in Sources */, @@ -4029,7 +4029,7 @@ F609600E9A88A4D44FD1FCEB /* FSTSpecTests.mm in Sources */, 42208EDA18C500BC271B6E95 /* FSTSyncEngineTestDriver.mm in Sources */, 5E5B3B8B3A41C8EB70035A6B /* FSTTransactionTests.mm in Sources */, - 8146D5979B2A0B63C79B7AC4 /* FSTUserDataConverterTests.mm in Sources */, + 75A176239B37354588769206 /* FSTUserDataReaderTests.mm in Sources */, 5E89B1A5A5430713C79C4854 /* FirestoreEncoderTests.swift in Sources */, 736C4E82689F1CA1859C4A3F /* XCTestCase+Await.mm in Sources */, EA46611779C3EEF12822508C /* annotations.pb.cc in Sources */, @@ -4079,8 +4079,6 @@ A1563EFEB021936D3FFE07E3 /* field_mask_test.cc in Sources */, B235E260EA0DCB7BAC04F69B /* field_path_test.cc in Sources */, 1BF1F9A0CBB6B01654D3C2BE /* field_transform_test.cc in Sources */, - 0A800CA749750B01E36A6787 /* field_value_benchmark.cc in Sources */, - 2D3401180516B739494C7EFC /* field_value_test.cc in Sources */, 199B778D5820495797E0BE02 /* filesystem_test.cc in Sources */, AD12205540893CEB48647937 /* filesystem_testing.cc in Sources */, D148475D7F26BFEE6E05CCDA /* firebase_credentials_provider_test.mm in Sources */, @@ -4127,8 +4125,9 @@ 85D61BDC7FB99B6E0DD3AFCA /* mutation.pb.cc in Sources */, C06E54352661FCFB91968640 /* mutation_queue_test.cc in Sources */, 795A0E11B3951ACEA2859C8A /* mutation_test.cc in Sources */, - E9430D3EBDAE12E9016B708F /* no_document_test.cc in Sources */, + 002EC02E9F86464049A69A06 /* nanopb_util_test.cc in Sources */, 2B4021C3E663DDDDD512E961 /* objc_type_traits_apple_test.mm in Sources */, + D711B3F495923680B6FC2FC6 /* object_value_test.cc in Sources */, 71702588BFBF5D3A670508E7 /* ordered_code_benchmark.cc in Sources */, B4C675BE9030D5C7D19C4D19 /* ordered_code_test.cc in Sources */, B3A309CCF5D75A555C7196E1 /* path_test.cc in Sources */, @@ -4176,6 +4175,7 @@ 44EAF3E6EAC0CC4EB2147D16 /* transform_operation_test.cc in Sources */, 3D22F56C0DE7C7256C75DC06 /* tree_sorted_map_test.cc in Sources */, 918E3D35942CE493690C45CE /* user_test.cc in Sources */, + 3DBB48F077C97200F32B51A0 /* value_util_test.cc in Sources */, 81A6B241E63540900F205817 /* view_snapshot_test.cc in Sources */, A5B8C273593D1BB6E8AE4CBA /* view_test.cc in Sources */, 7F771EB980D9CFAAB4764233 /* view_testing.cc in Sources */, @@ -4230,7 +4230,7 @@ 5492E03220213FFC00B64F25 /* FSTMockDatastore.mm in Sources */, 5492E03520213FFC00B64F25 /* FSTSpecTests.mm in Sources */, 5492E03320213FFC00B64F25 /* FSTSyncEngineTestDriver.mm in Sources */, - 548180A5228DEF1A004F70CD /* FSTUserDataConverterTests.mm in Sources */, + 6FC85C48CF8235BA1845E1C8 /* FSTUserDataReaderTests.mm in Sources */, 5492E03C2021401F00B64F25 /* XCTestCase+Await.mm in Sources */, 618BBEAF20B89AAC00B5BCE7 /* annotations.pb.cc in Sources */, 5467FB08203E6A44009C9584 /* app_testing.mm in Sources */, @@ -4279,8 +4279,6 @@ 549CCA5720A36E1F00BCEB75 /* field_mask_test.cc in Sources */, B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */, 2EC1C4D202A01A632339A161 /* field_transform_test.cc in Sources */, - 85D301119D7175F82E12892E /* field_value_benchmark.cc in Sources */, - AB356EF7200EA5EB0089B766 /* field_value_test.cc in Sources */, D94A1862B8FB778225DB54A1 /* filesystem_test.cc in Sources */, DD6C480629B3F87933FAF440 /* filesystem_testing.cc in Sources */, ABC1D7E42024AFDE00BA84F0 /* firebase_credentials_provider_test.mm in Sources */, @@ -4327,8 +4325,9 @@ 618BBEA820B89AAC00B5BCE7 /* mutation.pb.cc in Sources */, 1C4F88DDEFA6FA23E9E4DB4B /* mutation_queue_test.cc in Sources */, 32F022CB75AEE48CDDAF2982 /* mutation_test.cc in Sources */, - AB6B908820322E8800CC290A /* no_document_test.cc in Sources */, + 2EB2EE24076A4E4621E38E45 /* nanopb_util_test.cc in Sources */, C80B10E79CDD7EF7843C321E /* objc_type_traits_apple_test.mm in Sources */, + 1EE2B61B15AAA7C864188A59 /* object_value_test.cc in Sources */, 3040FD156E1B7C92B0F2A70C /* ordered_code_benchmark.cc in Sources */, AB380D04201BC6E400D97691 /* ordered_code_test.cc in Sources */, 5A080105CCBFDB6BF3F3772D /* path_test.cc in Sources */, @@ -4376,6 +4375,7 @@ D3CB03747E34D7C0365638F1 /* transform_operation_test.cc in Sources */, 549CCA5120A36DBC00BCEB75 /* tree_sorted_map_test.cc in Sources */, ABC1D7DE2023A05300BA84F0 /* user_test.cc in Sources */, + B844B264311E18051B1671ED /* value_util_test.cc in Sources */, 340987A77D72C80A3E0FDADF /* view_snapshot_test.cc in Sources */, 17473086EBACB98CDC3CC65C /* view_test.cc in Sources */, DDDE74C752E65DE7D39A7166 /* view_testing.cc in Sources */, @@ -4451,7 +4451,7 @@ 6E10507432E1D7AE658D16BD /* FSTSpecTests.mm in Sources */, 5FC0157A03EF9820BCCCC4A3 /* FSTSyncEngineTestDriver.mm in Sources */, 5492E07F202154EC00B64F25 /* FSTTransactionTests.mm in Sources */, - 2F7D76FF225B550F83B95A72 /* FSTUserDataConverterTests.mm in Sources */, + F5BDECEB3B43BD1591EEADBD /* FSTUserDataReaderTests.mm in Sources */, 6F45846C159D3C063DBD3CBE /* FirestoreEncoderTests.swift in Sources */, 5492E0442021457E00B64F25 /* XCTestCase+Await.mm in Sources */, 02EB33CC2590E1484D462912 /* annotations.pb.cc in Sources */, @@ -4501,8 +4501,6 @@ 6A40835DB2C02B9F07C02E88 /* field_mask_test.cc in Sources */, D00E69F7FDF2BE674115AD3F /* field_path_test.cc in Sources */, 9016EF298E41456060578C90 /* field_transform_test.cc in Sources */, - D541EA6C61FBB8913BA5C3C3 /* field_value_benchmark.cc in Sources */, - DA9FA01D1A4D7EC7ACA14DAB /* field_value_test.cc in Sources */, 280A282BE9AF4DCF4E855EAB /* filesystem_test.cc in Sources */, 867B370BF2DF84B6AB94B874 /* filesystem_testing.cc in Sources */, D085EA576C763E4146C9988E /* firebase_credentials_provider_test.mm in Sources */, @@ -4549,8 +4547,9 @@ C393D6984614D8E4D8C336A2 /* mutation.pb.cc in Sources */, A7399FB3BEC50BBFF08EC9BA /* mutation_queue_test.cc in Sources */, D18DBCE3FE34BF5F14CF8ABD /* mutation_test.cc in Sources */, - 9073AFB51EA26A818C29131E /* no_document_test.cc in Sources */, + 799AE5C2A38FCB435B1AB7EC /* nanopb_util_test.cc in Sources */, 0BC541D6457CBEDEA7BCF180 /* objc_type_traits_apple_test.mm in Sources */, + DF7ABEB48A650117CBEBCD26 /* object_value_test.cc in Sources */, 4FAB27F13EA5D3D79E770EA2 /* ordered_code_benchmark.cc in Sources */, 21836C4D9D48F962E7A3A244 /* ordered_code_test.cc in Sources */, 6105A1365831B79A7DEEA4F3 /* path_test.cc in Sources */, @@ -4598,6 +4597,7 @@ 60186935E36CF79E48A0B293 /* transform_operation_test.cc in Sources */, 5DA343D28AE05B0B2FE9FFB3 /* tree_sorted_map_test.cc in Sources */, D43F7601F3F3DE3125346D42 /* user_test.cc in Sources */, + EF79998EBE4C72B97AB1880E /* value_util_test.cc in Sources */, 59E89A97A476790E89AFC7E7 /* view_snapshot_test.cc in Sources */, B63D84B2980C7DEE7E6E4708 /* view_test.cc in Sources */, 48D1B38B93D34F1B82320577 /* view_testing.cc in Sources */, diff --git a/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm b/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm index f7d868951dc..a04867fff95 100644 --- a/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm +++ b/Firestore/Example/Tests/API/FIRQuerySnapshotTests.mm @@ -49,7 +49,6 @@ using firebase::firestore::model::DocumentComparator; using firebase::firestore::model::DocumentKeySet; using firebase::firestore::model::DocumentSet; -using firebase::firestore::model::DocumentState; using testutil::Doc; using testutil::DocSet; @@ -88,8 +87,8 @@ - (void)testEquals { } - (void)testIncludeMetadataChanges { - Document doc1Old = Doc("foo/bar", 1, Map("a", "b"), DocumentState::kLocalMutations); - Document doc1New = Doc("foo/bar", 1, Map("a", "b"), DocumentState::kSynced); + Document doc1Old = Doc("foo/bar", 1, Map("a", "b")).SetHasLocalMutations(); + Document doc1New = Doc("foo/bar", 1, Map("a", "b")); Document doc2Old = Doc("foo/baz", 1, Map("a", "b")); Document doc2New = Doc("foo/baz", 1, Map("a", "c")); diff --git a/Firestore/Example/Tests/API/FSTAPIHelpers.mm b/Firestore/Example/Tests/API/FSTAPIHelpers.mm index 6271f9325f4..a1b0e329ffa 100644 --- a/Firestore/Example/Tests/API/FSTAPIHelpers.mm +++ b/Firestore/Example/Tests/API/FSTAPIHelpers.mm @@ -31,11 +31,13 @@ #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRQuerySnapshot+Internal.h" #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/core/view_snapshot.h" #include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_set.h" +#include "Firestore/core/src/model/mutable_document.h" +#include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/remote/firebase_metadata_provider.h" #include "Firestore/core/src/util/string_apple.h" #include "Firestore/core/test/unit/testutil/testutil.h" @@ -45,13 +47,14 @@ using firebase::firestore::api::SnapshotMetadata; using firebase::firestore::core::DocumentViewChange; using firebase::firestore::core::ViewSnapshot; +using firebase::firestore::google_firestore_v1_Value; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::Document; using firebase::firestore::model::DocumentComparator; using firebase::firestore::model::DocumentKeySet; using firebase::firestore::model::DocumentSet; -using firebase::firestore::model::DocumentState; -using firebase::firestore::model::FieldValue; +using firebase::firestore::model::MutableDocument; +using firebase::firestore::nanopb::Message; using testutil::Doc; using testutil::Query; @@ -82,15 +85,15 @@ NSDictionary *_Nullable data, BOOL hasMutations, BOOL fromCache) { - absl::optional doc; + absl::optional doc; if (data) { - FSTUserDataConverter *converter = FSTTestUserDataConverter(); - FieldValue parsed = [converter parsedQueryValue:data]; + FSTUserDataReader *reader = FSTTestUserDataReader(); + Message parsed = [reader parsedQueryValue:data]; - doc = Doc(path, version, parsed, - hasMutations ? DocumentState::kLocalMutations : DocumentState::kSynced); + doc = Doc(path, version, std::move(parsed)); + if (hasMutations) doc->SetHasLocalMutations(); } - return [[FIRDocumentSnapshot alloc] initWithFirestore:FSTTestFirestore().wrapped + return [[FIRDocumentSnapshot alloc] initWithFirestore:FSTTestFirestore() documentKey:testutil::Key(path) document:doc fromCache:fromCache @@ -114,35 +117,34 @@ NSDictionary *> *docsToAdd, BOOL hasPendingWrites, BOOL fromCache) { - FSTUserDataConverter *converter = FSTTestUserDataConverter(); + FSTUserDataReader *reader = FSTTestUserDataReader(); SnapshotMetadata metadata(hasPendingWrites, fromCache); DocumentSet oldDocuments(DocumentComparator::ByKey()); DocumentKeySet mutatedKeys; for (NSString *key in oldDocs) { - FieldValue doc = [converter parsedQueryValue:oldDocs[key]]; + Message value = [reader parsedQueryValue:oldDocs[key]]; std::string documentKey = util::StringFormat("%s/%s", path, key); - oldDocuments = oldDocuments.insert( - Doc(documentKey, 1, doc, - hasPendingWrites ? DocumentState::kLocalMutations : DocumentState::kSynced)); + MutableDocument doc = Doc(documentKey, 1, std::move(value)); if (hasPendingWrites) { mutatedKeys = mutatedKeys.insert(testutil::Key(documentKey)); + doc.SetHasLocalMutations(); } + oldDocuments = oldDocuments.insert(doc); } DocumentSet newDocuments = oldDocuments; std::vector documentChanges; for (NSString *key in docsToAdd) { - FieldValue doc = [converter parsedQueryValue:docsToAdd[key]]; + Message value = [reader parsedQueryValue:docsToAdd[key]]; std::string documentKey = util::StringFormat("%s/%s", path, key); - Document docToAdd = - Doc(documentKey, 1, doc, - hasPendingWrites ? DocumentState::kLocalMutations : DocumentState::kSynced); - newDocuments = newDocuments.insert(docToAdd); - documentChanges.emplace_back(docToAdd, DocumentViewChange::Type::Added); + MutableDocument doc = Doc(documentKey, 1, std::move(value)); + documentChanges.emplace_back(doc, DocumentViewChange::Type::Added); if (hasPendingWrites) { mutatedKeys = mutatedKeys.insert(testutil::Key(documentKey)); + doc.SetHasLocalMutations(); } + newDocuments = newDocuments.insert(doc); } ViewSnapshot viewSnapshot{Query(path), newDocuments, diff --git a/Firestore/Example/Tests/API/FSTUserDataConverterTests.mm b/Firestore/Example/Tests/API/FSTUserDataReaderTests.mm similarity index 60% rename from Firestore/Example/Tests/API/FSTUserDataConverterTests.mm rename to Firestore/Example/Tests/API/FSTUserDataReaderTests.mm index e8d0496a6b2..70291cae607 100644 --- a/Firestore/Example/Tests/API/FSTUserDataConverterTests.mm +++ b/Firestore/Example/Tests/API/FSTUserDataReaderTests.mm @@ -14,7 +14,7 @@ * limitations under the License. */ -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #import #import @@ -24,33 +24,49 @@ #import "Firestore/Example/Tests/Util/FSTHelpers.h" #import "Firestore/Source/API/converters.h" +#include "Firestore/core/include/firebase/firestore/geo_point.h" #include "Firestore/core/src/model/database_id.h" -#include "Firestore/core/src/model/field_value.h" #include "Firestore/core/src/model/patch_mutation.h" #include "Firestore/core/src/model/set_mutation.h" #include "Firestore/core/src/model/transform_operation.h" +#include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/nanopb/nanopb_util.h" #include "Firestore/core/test/unit/testutil/testutil.h" namespace util = firebase::firestore::util; +namespace nanopb = firebase::firestore::nanopb; +using firebase::Timestamp; +using firebase::firestore::GeoPoint; using firebase::firestore::api::MakeGeoPoint; using firebase::firestore::api::MakeTimestamp; +using firebase::firestore::google_firestore_v1_ArrayValue; +using firebase::firestore::google_firestore_v1_Value; using firebase::firestore::model::ArrayTransform; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::FieldPath; -using firebase::firestore::model::FieldValue; using firebase::firestore::model::FieldTransform; +using firebase::firestore::model::GetTypeOrder; +using firebase::firestore::model::IsArray; +using firebase::firestore::model::IsNullValue; +using firebase::firestore::model::IsNumber; using firebase::firestore::model::ObjectValue; using firebase::firestore::model::PatchMutation; -using firebase::firestore::model::TransformOperation; +using firebase::firestore::model::RefValue; using firebase::firestore::model::SetMutation; +using firebase::firestore::model::TransformOperation; +using firebase::firestore::model::TypeOrder; using firebase::firestore::nanopb::MakeNSData; +using firebase::firestore::nanopb::Message; +using firebase::firestore::testutil::Array; using firebase::firestore::testutil::Field; +using firebase::firestore::testutil::Map; +using firebase::firestore::testutil::Value; +using firebase::firestore::testutil::WrapObject; -@interface FSTUserDataConverterTests : XCTestCase +@interface FSTUserDataReaderTests : XCTestCase @end -@implementation FSTUserDataConverterTests +@implementation FSTUserDataReaderTests - (void)testConvertsIntegers { NSArray *values = @[ @@ -58,9 +74,9 @@ - (void)testConvertsIntegers { @(LONG_MIN), @(LONG_MAX), @(LLONG_MIN), @(LLONG_MAX) // Larger values ]; for (NSNumber *value in values) { - FieldValue wrapped = FSTTestFieldValue(value); - XCTAssertEqual(wrapped.type(), FieldValue::Type::Integer); - XCTAssertEqual(wrapped.integer_value(), [value longLongValue]); + Message wrapped = FSTTestFieldValue(value); + XCTAssertTrue(IsNumber(*wrapped)); + XCTAssertEqual(wrapped->integer_value, [value longLongValue]); } } @@ -72,36 +88,37 @@ - (void)testConvertsDoubles { @(0x1.0p-1074), @(DBL_MIN), @(1.1), @(LLONG_MAX * 1.0), @(DBL_MAX), @(INFINITY) ]; for (NSNumber *value in values) { - FieldValue wrapped = FSTTestFieldValue(value); - XCTAssertEqual(wrapped.type(), FieldValue::Type::Double); - XCTAssertEqual(wrapped.double_value(), [value doubleValue]); + Message wrapped = FSTTestFieldValue(value); + XCTAssertTrue(IsNumber(*wrapped)); + XCTAssertEqual(wrapped->double_value, [value doubleValue]); } } - (void)testConvertsNilAndNSNull { - FieldValue nullValue = FieldValue::Null(); - XCTAssertEqual(nullValue.type(), FieldValue::Type::Null); - XCTAssertEqual(FSTTestFieldValue(nil), nullValue); - XCTAssertEqual(FSTTestFieldValue([NSNull null]), nullValue); + Message nullValue = Value(nullptr); + XCTAssertTrue(IsNullValue(*nullValue)); + XCTAssertEqual(*FSTTestFieldValue(nil), *nullValue); + XCTAssertEqual(*FSTTestFieldValue([NSNull null]), *nullValue); } - (void)testConvertsBooleans { NSArray *values = @[ @YES, @NO ]; for (NSNumber *value in values) { - FieldValue wrapped = FSTTestFieldValue(value); - XCTAssertEqual(wrapped.type(), FieldValue::Type::Boolean); - XCTAssertEqual(wrapped.boolean_value(), [value boolValue]); + Message wrapped = FSTTestFieldValue(value); + XCTAssertEqual(GetTypeOrder(*wrapped), TypeOrder::kBoolean); + XCTAssertEqual(wrapped->boolean_value, [value boolValue]); } } - (void)testConvertsUnsignedCharToInteger { - // See comments in FSTUserDataConverter regarding handling of signed char. Essentially, signed + // See comments in FSTUserDataReader regarding handling of signed char. Essentially, signed // char has to be treated as boolean. Unsigned chars could conceivably be handled consistently // with signed chars but on arm64 these end up being stored as signed shorts. This forces us to // choose, and it's more useful to support shorts as Integers than it is to treat unsigned char as // Boolean. - FieldValue wrapped = FSTTestFieldValue([NSNumber numberWithUnsignedChar:1]); - XCTAssertEqual(wrapped, FieldValue::FromInteger(1)); + Message wrapped = + FSTTestFieldValue([NSNumber numberWithUnsignedChar:1]); + XCTAssertEqual(*wrapped, *Value(1)); } union DoubleBits { @@ -112,9 +129,9 @@ - (void)testConvertsUnsignedCharToInteger { - (void)testConvertsStrings { NSArray *values = @[ @"", @"abc" ]; for (id value in values) { - FieldValue wrapped = FSTTestFieldValue(value); - XCTAssertEqual(wrapped.type(), FieldValue::Type::String); - XCTAssertEqual(wrapped.string_value(), util::MakeString(value)); + Message wrapped = FSTTestFieldValue(value); + XCTAssertEqual(GetTypeOrder(*wrapped), TypeOrder::kString); + XCTAssertEqual(nanopb::MakeString(wrapped->string_value), util::MakeString(value)); } } @@ -122,9 +139,11 @@ - (void)testConvertsDates { NSArray *values = @[ FSTTestDate(1900, 12, 1, 1, 20, 30), FSTTestDate(2017, 4, 24, 13, 20, 30) ]; for (NSDate *value in values) { - FieldValue wrapped = FSTTestFieldValue(value); - XCTAssertEqual(wrapped.type(), FieldValue::Type::Timestamp); - XCTAssertEqual(wrapped.timestamp_value(), MakeTimestamp(value)); + Message wrapped = FSTTestFieldValue(value); + XCTAssertEqual(GetTypeOrder(*wrapped), TypeOrder::kTimestamp); + Timestamp timestamp = MakeTimestamp(value); + XCTAssertEqual(wrapped->timestamp_value.nanos, timestamp.nanoseconds()); + XCTAssertEqual(wrapped->timestamp_value.seconds, timestamp.seconds()); } } @@ -132,18 +151,20 @@ - (void)testConvertsGeoPoints { NSArray *values = @[ FSTTestGeoPoint(1.24, 4.56), FSTTestGeoPoint(-20, 100) ]; for (FIRGeoPoint *value in values) { - FieldValue wrapped = FSTTestFieldValue(value); - XCTAssertEqual(wrapped.type(), FieldValue::Type::GeoPoint); - XCTAssertEqual(wrapped.geo_point_value(), MakeGeoPoint(value)); + Message wrapped = FSTTestFieldValue(value); + XCTAssertEqual(GetTypeOrder(*wrapped), TypeOrder::kGeoPoint); + GeoPoint geo_point = MakeGeoPoint(value); + XCTAssertEqual(wrapped->geo_point_value.longitude, geo_point.longitude()); + XCTAssertEqual(wrapped->geo_point_value.latitude, geo_point.latitude()); } } - (void)testConvertsBlobs { NSArray *values = @[ FSTTestData(1, 2, 3, -1), FSTTestData(1, 2, -1) ]; for (NSData *value in values) { - FieldValue wrapped = FSTTestFieldValue(value); - XCTAssertEqual(wrapped.type(), FieldValue::Type::Blob); - XCTAssertEqualObjects(MakeNSData(wrapped.blob_value()), value); + Message wrapped = FSTTestFieldValue(value); + XCTAssertEqual(GetTypeOrder(*wrapped), TypeOrder::kBlob); + XCTAssertEqualObjects(MakeNSData(wrapped->bytes_value), value); } } @@ -153,69 +174,60 @@ - (void)testConvertsResourceNames { FSTTestRef("project", DatabaseId::kDefault, @"foo/baz") ]; for (FSTDocumentKeyReference *value in values) { - FieldValue wrapped = FSTTestFieldValue(value); - XCTAssertEqual(wrapped.type(), FieldValue::Type::Reference); - XCTAssertEqual(wrapped.reference_value().key(), value.key); - XCTAssertTrue(wrapped.reference_value().database_id() == value.databaseID); + Message wrapped = FSTTestFieldValue(value); + XCTAssertEqual(GetTypeOrder(*wrapped), TypeOrder::kReference); + Message expected = RefValue(value.databaseID, value.key); + XCTAssertEqual(*wrapped, *expected); } } - (void)testConvertsEmptyObjects { - XCTAssertEqual(ObjectValue(FSTTestFieldValue(@{})), ObjectValue::Empty()); - XCTAssertEqual(FSTTestFieldValue(@{}).type(), FieldValue::Type::Object); + XCTAssertTrue(ObjectValue(FSTTestFieldValue(@{})) == ObjectValue{}); + XCTAssertEqual(GetTypeOrder(*FSTTestFieldValue(@{})), TypeOrder::kMap); } - (void)testConvertsSimpleObjects { ObjectValue actual = FSTTestObjectValue(@{@"a" : @"foo", @"b" : @(1L), @"c" : @YES, @"d" : [NSNull null]}); - ObjectValue expected = ObjectValue::FromMap({{"a", FieldValue::FromString("foo")}, - {"b", FieldValue::FromInteger(1)}, - {"c", FieldValue::True()}, - {"d", FieldValue::Null()}}); + ObjectValue expected = WrapObject("a", "foo", "b", 1, "c", true, "d", nullptr); XCTAssertEqual(actual, expected); - XCTAssertEqual(actual.AsFieldValue().type(), FieldValue::Type::Object); } - (void)testConvertsNestedObjects { ObjectValue actual = FSTTestObjectValue(@{@"a" : @{@"b" : @{@"c" : @"foo"}, @"d" : @YES}}); - ObjectValue expected = ObjectValue::FromMap({ - {"a", - ObjectValue::FromMap({{"b", ObjectValue::FromMap({{"c", FieldValue::FromString("foo")}})}, - {"d", FieldValue::True()}})}, - }); + ObjectValue expected = WrapObject("a", Map("b", Map("c", "foo"), "d", true)); XCTAssertEqual(actual, expected); - XCTAssertEqual(actual.AsFieldValue().type(), FieldValue::Type::Object); } - (void)testConvertsArrays { - FieldValue expected = FieldValue::FromArray({ - FieldValue::FromString("value"), - FieldValue::True(), - }); + Message expected = Value(Array("value", true)); - FieldValue actual = (FieldValue)FSTTestFieldValue(@[ @"value", @YES ]); - XCTAssertEqual(actual, expected); - XCTAssertEqual(actual.type(), FieldValue::Type::Array); + Message actual = FSTTestFieldValue(@[ @"value", @YES ]); + XCTAssertEqual(*actual, *expected); + XCTAssertTrue(IsArray(*actual)); } - (void)testNSDatesAreConvertedToTimestamps { NSDate *date = [NSDate date]; + Timestamp timestamp = MakeTimestamp(date); id input = @{@"array" : @[ @1, date ], @"obj" : @{@"date" : date, @"string" : @"hi"}}; ObjectValue value = FSTTestObjectValue(input); { auto array = value.Get(Field("array")); XCTAssertTrue(array.has_value()); - XCTAssertEqual(array->type(), FieldValue::Type::Array); + XCTAssertEqual(GetTypeOrder(*array), TypeOrder::kArray); - const FieldValue &actual = array->array_value()[1]; - XCTAssertEqual(actual.type(), FieldValue::Type::Timestamp); - XCTAssertEqual(actual.timestamp_value(), MakeTimestamp(date)); + const google_firestore_v1_Value &actual = array->array_value.values[1]; + XCTAssertEqual(GetTypeOrder(actual), TypeOrder::kTimestamp); + XCTAssertEqual(actual.timestamp_value.seconds, timestamp.seconds()); + XCTAssertEqual(actual.timestamp_value.nanos, timestamp.nanoseconds()); } { auto found = value.Get(Field("obj.date")); XCTAssertTrue(found.has_value()); - XCTAssertEqual(found->type(), FieldValue::Type::Timestamp); - XCTAssertEqual(found->timestamp_value(), MakeTimestamp(date)); + XCTAssertEqual(GetTypeOrder(*found), TypeOrder::kTimestamp); + XCTAssertEqual(found->timestamp_value.seconds, timestamp.seconds()); + XCTAssertEqual(found->timestamp_value.nanos, timestamp.nanoseconds()); } } @@ -239,8 +251,8 @@ - (void)testCreatesArrayUnionTransforms { const FieldTransform &setFirst = setMutation.field_transforms()[0]; XCTAssertEqual(setFirst.path(), FieldPath({"foo"})); { - std::vector expectedElements{FSTTestFieldValue(@"tag")}; - ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements); + Message expectedElements = Array(FSTTestFieldValue(@"tag")); + ArrayTransform expected(TransformOperation::Type::ArrayUnion, std::move(expectedElements)); XCTAssertEqual(static_cast(patchFirst.transformation()), expected); XCTAssertEqual(static_cast(setFirst.transformation()), expected); } @@ -250,10 +262,10 @@ - (void)testCreatesArrayUnionTransforms { const FieldTransform &setSecond = setMutation.field_transforms()[1]; XCTAssertEqual(setSecond.path(), FieldPath({"bar"})); { - std::vector expectedElements { - FSTTestFieldValue(@YES), FSTTestFieldValue(@{@"nested" : @{@"a" : @[ @1, @2 ]}}) - }; - ArrayTransform expected(TransformOperation::Type::ArrayUnion, expectedElements); + Message expectedElements = + Array(FSTTestFieldValue(@YES), FSTTestFieldValue( + @{@"nested" : @{@"a" : @[ @1, @2 ]}})); + ArrayTransform expected(TransformOperation::Type::ArrayUnion, std::move(expectedElements)); XCTAssertEqual(static_cast(patchSecond.transformation()), expected); XCTAssertEqual(static_cast(setSecond.transformation()), expected); } @@ -276,8 +288,9 @@ - (void)testCreatesArrayRemoveTransforms { const FieldTransform &setFirst = setMutation.field_transforms()[0]; XCTAssertEqual(setFirst.path(), FieldPath({"foo"})); { - std::vector expectedElements{FSTTestFieldValue(@"tag")}; - const ArrayTransform expected(TransformOperation::Type::ArrayRemove, expectedElements); + Message expectedElements = Array(FSTTestFieldValue(@"tag")); + const ArrayTransform expected(TransformOperation::Type::ArrayRemove, + std::move(expectedElements)); XCTAssertEqual(static_cast(patchFirst.transformation()), expected); XCTAssertEqual(static_cast(setFirst.transformation()), expected); } diff --git a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm index 4862ac2b83f..5510a82912b 100644 --- a/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm +++ b/Firestore/Example/Tests/Integration/FSTDatastoreTests.mm @@ -23,7 +23,7 @@ #include #import "Firestore/Source/API/FIRDocumentReference+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h" @@ -61,6 +61,7 @@ using firebase::firestore::auth::EmptyCredentialsProvider; using firebase::firestore::auth::User; using firebase::firestore::core::DatabaseInfo; +using firebase::firestore::google_firestore_v1_Value; using firebase::firestore::local::LocalStore; using firebase::firestore::local::MemoryPersistence; using firebase::firestore::local::Persistence; @@ -139,8 +140,8 @@ - (void)expectListenEventWithDescription:(NSString *)description { description]]]; } -- (void)applySuccessfulWriteWithResult:(const MutationBatchResult &)batchResult { - _writeEvents.push_back(batchResult); +- (void)applySuccessfulWriteWithResult:(MutationBatchResult)batchResult { + _writeEvents.push_back(std::move(batchResult)); XCTestExpectation *expectation = [self.writeEventExpectations objectAtIndex:0]; [self.writeEventExpectations removeObjectAtIndex:0]; [expectation fulfill]; @@ -189,8 +190,8 @@ void HandleRejectedListen(TargetId target_id, Status error) override { [underlying_capture_ rejectListenWithTargetID:target_id error:error.ToNSError()]; } - void HandleSuccessfulWrite(const MutationBatchResult &batch_result) override { - [underlying_capture_ applySuccessfulWriteWithResult:batch_result]; + void HandleSuccessfulWrite(MutationBatchResult batch_result) override { + [underlying_capture_ applySuccessfulWriteWithResult:std::move(batch_result)]; } void HandleRejectedWrite(BatchId batch_id, Status error) override { diff --git a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm index c097538b0e2..bf9952687cf 100644 --- a/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm +++ b/Firestore/Example/Tests/SpecTests/FSTSpecTests.mm @@ -31,7 +31,7 @@ #include #include -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #import "Firestore/Example/Tests/SpecTests/FSTSyncEngineTestDriver.h" #import "Firestore/Example/Tests/Util/FSTHelpers.h" @@ -48,14 +48,13 @@ #include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/document_key_set.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/model/maybe_document.h" -#include "Firestore/core/src/model/no_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/patch_mutation.h" #include "Firestore/core/src/model/resource_path.h" #include "Firestore/core/src/model/set_mutation.h" #include "Firestore/core/src/model/snapshot_version.h" #include "Firestore/core/src/model/types.h" +#include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/nanopb/nanopb_util.h" #include "Firestore/core/src/remote/existence_filter.h" #include "Firestore/core/src/remote/serializer.h" @@ -77,32 +76,33 @@ namespace objc = firebase::firestore::objc; namespace testutil = firebase::firestore::testutil; namespace util = firebase::firestore::util; -using firebase::firestore::api::LoadBundleTask; using firebase::firestore::Error; +using firebase::firestore::api::LoadBundleTask; using firebase::firestore::auth::User; using firebase::firestore::bundle::BundleReader; using firebase::firestore::bundle::BundleSerializer; using firebase::firestore::core::DocumentViewChange; using firebase::firestore::core::Query; +using firebase::firestore::google_firestore_v1_ArrayValue; +using firebase::firestore::google_firestore_v1_Value; using firebase::firestore::local::Persistence; -using firebase::firestore::local::TargetData; using firebase::firestore::local::QueryPurpose; +using firebase::firestore::local::TargetData; +using firebase::firestore::model::Document; using firebase::firestore::model::Document; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::DocumentKeySet; -using firebase::firestore::model::DocumentState; -using firebase::firestore::model::FieldValue; -using firebase::firestore::model::MaybeDocument; +using firebase::firestore::model::MutableDocument; using firebase::firestore::model::MutationResult; -using firebase::firestore::model::NoDocument; using firebase::firestore::model::ObjectValue; using firebase::firestore::model::ResourcePath; using firebase::firestore::model::SnapshotVersion; using firebase::firestore::model::TargetId; using firebase::firestore::nanopb::ByteString; using firebase::firestore::nanopb::MakeByteString; -using firebase::firestore::remote::ExistenceFilter; +using firebase::firestore::nanopb::Message; using firebase::firestore::remote::DocumentWatchChange; +using firebase::firestore::remote::ExistenceFilter; using firebase::firestore::remote::ExistenceFilterWatchChange; using firebase::firestore::remote::WatchTargetChange; using firebase::firestore::remote::WatchTargetChangeState; @@ -204,7 +204,7 @@ @implementation FSTSpecTests { BOOL _gcEnabled; size_t _maxConcurrentLimboResolutions; BOOL _networkEnabled; - FSTUserDataConverter *_converter; + FSTUserDataReader *_reader; std::shared_ptr user_executor_; } @@ -230,7 +230,7 @@ - (BOOL)shouldRunWithTags:(NSArray *)tags { } - (void)setUpForSpecWithConfig:(NSDictionary *)config { - _converter = FSTTestUserDataConverter(); + _reader = FSTTestUserDataReader(); std::unique_ptr user_executor = util::Executor::CreateSerial("user executor"); user_executor_ = absl::ShareUniquePtr(std::move(user_executor)); @@ -298,8 +298,8 @@ - (Query)parseQuery:(id)querySpec { for (NSArray *filter in filters) { std::string key = util::MakeString(filter[0]); std::string op = util::MakeString(filter[1]); - FieldValue value = [_converter parsedQueryValue:filter[2]]; - query = query.AddingFilter(Filter(key, op, value)); + Message value = [_reader parsedQueryValue:filter[2]]; + query = query.AddingFilter(Filter(key, op, std::move(value))); } } @@ -325,17 +325,17 @@ - (SnapshotVersion)parseVersion:(NSNumber *_Nullable)version { - (DocumentViewChange)parseChange:(NSDictionary *)jsonDoc ofType:(DocumentViewChange::Type)type { NSNumber *version = jsonDoc[@"version"]; NSDictionary *options = jsonDoc[@"options"]; - DocumentState documentState = [options[@"hasLocalMutations"] isEqualToNumber:@YES] - ? DocumentState::kLocalMutations - : ([options[@"hasCommittedMutations"] isEqualToNumber:@YES] - ? DocumentState::kCommittedMutations - : DocumentState::kSynced); XCTAssert([jsonDoc[@"key"] isKindOfClass:[NSString class]]); - FieldValue data = [_converter parsedQueryValue:jsonDoc[@"value"]]; - Document doc = Doc(util::MakeString((NSString *)jsonDoc[@"key"]), version.longLongValue, data, - documentState); - return DocumentViewChange{doc, type}; + Message data = [_reader parsedQueryValue:jsonDoc[@"value"]]; + MutableDocument doc = + Doc(util::MakeString((NSString *)jsonDoc[@"key"]), version.longLongValue, std::move(data)); + if ([options[@"hasLocalMutations"] boolValue] == YES) { + doc.SetHasLocalMutations(); + } else if ([options[@"hasCommittedMutations"] boolValue] == YES) { + doc.SetHasCommittedMutations(); + } + return DocumentViewChange{std::move(doc), type}; } #pragma mark - Methods for doing the steps of the spec test. @@ -440,11 +440,11 @@ - (void)doWatchEntity:(NSDictionary *)watchEntity { ? absl::optional{} : FSTTestObjectValue(docSpec[@"value"]); SnapshotVersion version = [self parseVersion:docSpec[@"version"]]; - MaybeDocument doc; + MutableDocument doc; if (value) { - doc = Document(*std::move(value), key, version, DocumentState::kSynced); + doc = MutableDocument::FoundDocument(key, version, *std::move(value)); } else { - doc = NoDocument(key, version, /* has_committed_mutations= */ false); + doc = MutableDocument::NoDocument(key, version); } DocumentWatchChange change{ConvertTargetsArray(watchEntity[@"targets"]), ConvertTargetsArray(watchEntity[@"removedTargets"]), std::move(key), @@ -506,8 +506,10 @@ - (void)doWriteAck:(NSDictionary *)spec { @"'keepInQueue=true' is not supported on iOS and should only be set in " @"multi-client tests"); - MutationResult mutationResult(version, absl::nullopt); - [self.driver receiveWriteAckWithVersion:version mutationResults:{mutationResult}]; + MutationResult mutationResult(version, Message{}); + std::vector mutationResults; + mutationResults.emplace_back(std::move(mutationResult)); + [self.driver receiveWriteAckWithVersion:version mutationResults:std::move(mutationResults)]; } - (void)doFailWrite:(NSDictionary *)spec { diff --git a/Firestore/Example/Tests/Util/FSTHelpers.h b/Firestore/Example/Tests/Util/FSTHelpers.h index ce6a58c5217..e851d4f7202 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.h +++ b/Firestore/Example/Tests/Util/FSTHelpers.h @@ -19,12 +19,14 @@ #include #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/model/model_fwd.h" +#include "Firestore/core/src/nanopb/message.h" #include "absl/strings/string_view.h" @class FIRGeoPoint; @class FSTDocumentKeyReference; -@class FSTUserDataConverter; +@class FSTUserDataReader; namespace model = firebase::firestore::model; @@ -93,7 +95,7 @@ NSData *FSTTestData(int bytes, ...); FIRGeoPoint *FSTTestGeoPoint(double latitude, double longitude); /** Creates a user data converter set up for a generic project. */ -FSTUserDataConverter *FSTTestUserDataConverter(); +FSTUserDataReader *FSTTestUserDataReader(); /** * Creates a new NSDateComponents from components. Note that year, month, and day are all @@ -102,8 +104,9 @@ FSTUserDataConverter *FSTTestUserDataConverter(); NSDateComponents *FSTTestDateComponents( int year, int month, int day, int hour, int minute, int second); -/** Wraps a plain value into an FieldValue instance. */ -model::FieldValue FSTTestFieldValue(id _Nullable value); +/** Wraps a plain value into a Message proto. */ +firebase::firestore::nanopb::Message + FSTTestFieldValue(id _Nullable value); /** Wraps a NSDictionary value into an ObjectValue instance. */ model::ObjectValue FSTTestObjectValue(NSDictionary *data); diff --git a/Firestore/Example/Tests/Util/FSTHelpers.mm b/Firestore/Example/Tests/Util/FSTHelpers.mm index 1802ff69e80..20b22dfdee5 100644 --- a/Firestore/Example/Tests/Util/FSTHelpers.mm +++ b/Firestore/Example/Tests/Util/FSTHelpers.mm @@ -22,13 +22,14 @@ #include #include -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/core/user_data.h" #include "Firestore/core/src/model/delete_mutation.h" #include "Firestore/core/src/model/patch_mutation.h" #include "Firestore/core/src/model/resource_path.h" #include "Firestore/core/src/model/set_mutation.h" +#include "Firestore/core/src/model/value_util.h" #import "Firestore/core/test/unit/testutil/testutil.h" @@ -37,15 +38,19 @@ using firebase::firestore::core::ParsedSetData; using firebase::firestore::core::ParsedUpdateData; +using firebase::firestore::google_firestore_v1_Value; using firebase::firestore::model::DatabaseId; using firebase::firestore::model::DeleteMutation; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldPath; -using firebase::firestore::model::FieldValue; +using firebase::firestore::model::GetTypeOrder; using firebase::firestore::model::ObjectValue; +using firebase::firestore::model::Mutation; using firebase::firestore::model::PatchMutation; using firebase::firestore::model::Precondition; using firebase::firestore::model::SetMutation; +using firebase::firestore::model::TypeOrder; +using firebase::firestore::nanopb::Message; NS_ASSUME_NONNULL_BEGIN @@ -93,25 +98,25 @@ return comps; } -FSTUserDataConverter *FSTTestUserDataConverter() { - FSTUserDataConverter *converter = - [[FSTUserDataConverter alloc] initWithDatabaseID:DatabaseId("project") - preConverter:^id _Nullable(id _Nullable input) { - return input; - }]; - return converter; +FSTUserDataReader *FSTTestUserDataReader() { + FSTUserDataReader *reader = + [[FSTUserDataReader alloc] initWithDatabaseID:DatabaseId("project") + preConverter:^id _Nullable(id _Nullable input) { + return input; + }]; + return reader; } -FieldValue FSTTestFieldValue(id _Nullable value) { - FSTUserDataConverter *converter = FSTTestUserDataConverter(); +Message FSTTestFieldValue(id _Nullable value) { + FSTUserDataReader *reader = FSTTestUserDataReader(); // HACK: We use parsedQueryValue: since it accepts scalars as well as arrays / objects, and // our tests currently use FSTTestFieldValue() pretty generically so we don't know the intent. - return [converter parsedQueryValue:value]; + return [reader parsedQueryValue:value]; } ObjectValue FSTTestObjectValue(NSDictionary *data) { - FieldValue wrapped = FSTTestFieldValue(data); - HARD_ASSERT(wrapped.type() == FieldValue::Type::Object, "Unsupported value: %s", data); + Message wrapped = FSTTestFieldValue(data); + HARD_ASSERT(GetTypeOrder(*wrapped) == TypeOrder::kMap, "Unsupported value: %s", data); return ObjectValue(std::move(wrapped)); } @@ -125,10 +130,10 @@ DocumentKey FSTTestDocKey(NSString *path) { } SetMutation FSTTestSetMutation(NSString *path, NSDictionary *values) { - FSTUserDataConverter *converter = FSTTestUserDataConverter(); - ParsedSetData result = [converter parsedSetData:values]; - return SetMutation(FSTTestDocKey(path), result.data(), Precondition::None(), - result.field_transforms()); + FSTUserDataReader *reader = FSTTestUserDataReader(); + Mutation mutation = + [reader parsedSetData:values].ToMutation(FSTTestDocKey(path), Precondition::None()); + return SetMutation(mutation); } PatchMutation FSTTestPatchMutation(NSString *path, @@ -143,15 +148,14 @@ PatchMutation FSTTestPatchMutation(NSString *path, } }]; - FSTUserDataConverter *converter = FSTTestUserDataConverter(); - ParsedUpdateData parsed = [converter parsedUpdateData:mutableValues]; - DocumentKey key = FSTTestDocKey(path); - BOOL merge = !updateMask.empty(); Precondition precondition = merge ? Precondition::None() : Precondition::Exists(true); - return PatchMutation(key, parsed.data(), parsed.fieldMask(), precondition, - parsed.field_transforms()); + + FSTUserDataReader *reader = FSTTestUserDataReader(); + Mutation mutation = + [reader parsedUpdateData:mutableValues].ToMutation(FSTTestDocKey(path), precondition); + return PatchMutation(mutation); } DeleteMutation FSTTestDeleteMutation(NSString *path) { diff --git a/Firestore/Source/API/FIRCollectionReference.mm b/Firestore/Source/API/FIRCollectionReference.mm index dd063c944e3..54e3bc9d015 100644 --- a/Firestore/Source/API/FIRCollectionReference.mm +++ b/Firestore/Source/API/FIRCollectionReference.mm @@ -21,7 +21,7 @@ #import "Firestore/Source/API/FIRDocumentReference+Internal.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRQuery+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/api/collection_reference.h" #include "Firestore/core/src/api/document_reference.h" @@ -118,7 +118,7 @@ - (FIRDocumentReference *)addDocumentWithData:(NSDictionary *)da - (FIRDocumentReference *)addDocumentWithData:(NSDictionary *)data completion: (nullable void (^)(NSError *_Nullable error))completion { - ParsedSetData parsed = [self.firestore.dataConverter parsedSetData:data]; + ParsedSetData parsed = [self.firestore.dataReader parsedSetData:data]; DocumentReference docRef = self.reference.AddDocument(std::move(parsed), util::MakeCallback(completion)); return [[FIRDocumentReference alloc] initWithReference:std::move(docRef)]; diff --git a/Firestore/Source/API/FIRDocumentReference.mm b/Firestore/Source/API/FIRDocumentReference.mm index 1b1c15a9c16..b9bee8016e4 100644 --- a/Firestore/Source/API/FIRDocumentReference.mm +++ b/Firestore/Source/API/FIRDocumentReference.mm @@ -26,7 +26,7 @@ #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRFirestoreSource+Internal.h" #import "Firestore/Source/API/FIRListenerRegistration+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/api/collection_reference.h" #include "Firestore/core/src/api/document_reference.h" @@ -159,17 +159,17 @@ - (void)setData:(NSDictionary *)documentData - (void)setData:(NSDictionary *)documentData merge:(BOOL)merge completion:(nullable void (^)(NSError *_Nullable error))completion { - auto dataConverter = self.firestore.dataConverter; - ParsedSetData parsed = merge ? [dataConverter parsedMergeData:documentData fieldMask:nil] - : [dataConverter parsedSetData:documentData]; + auto dataReader = self.firestore.dataReader; + ParsedSetData parsed = merge ? [dataReader parsedMergeData:documentData fieldMask:nil] + : [dataReader parsedSetData:documentData]; _documentReference.SetData(std::move(parsed), util::MakeCallback(completion)); } - (void)setData:(NSDictionary *)documentData mergeFields:(NSArray *)mergeFields completion:(nullable void (^)(NSError *_Nullable error))completion { - ParsedSetData parsed = [self.firestore.dataConverter parsedMergeData:documentData - fieldMask:mergeFields]; + ParsedSetData parsed = [self.firestore.dataReader parsedMergeData:documentData + fieldMask:mergeFields]; _documentReference.SetData(std::move(parsed), util::MakeCallback(completion)); } @@ -179,7 +179,7 @@ - (void)updateData:(NSDictionary *)fields { - (void)updateData:(NSDictionary *)fields completion:(nullable void (^)(NSError *_Nullable error))completion { - ParsedUpdateData parsed = [self.firestore.dataConverter parsedUpdateData:fields]; + ParsedUpdateData parsed = [self.firestore.dataReader parsedUpdateData:fields]; _documentReference.UpdateData(std::move(parsed), util::MakeCallback(completion)); } diff --git a/Firestore/Source/API/FIRDocumentSnapshot+Internal.h b/Firestore/Source/API/FIRDocumentSnapshot+Internal.h index 8b24fce6a15..103f5cc471f 100644 --- a/Firestore/Source/API/FIRDocumentSnapshot+Internal.h +++ b/Firestore/Source/API/FIRDocumentSnapshot+Internal.h @@ -21,6 +21,8 @@ #include "Firestore/core/src/api/api_fwd.h" #include "Firestore/core/src/model/model_fwd.h" +@class FIRFirestore; + namespace api = firebase::firestore::api; namespace model = firebase::firestore::model; @@ -30,12 +32,12 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithSnapshot:(api::DocumentSnapshot &&)snapshot NS_DESIGNATED_INITIALIZER; -- (instancetype)initWithFirestore:(std::shared_ptr)firestore +- (instancetype)initWithFirestore:(FIRFirestore *)firestore documentKey:(model::DocumentKey)documentKey document:(const absl::optional &)document metadata:(api::SnapshotMetadata)metadata; -- (instancetype)initWithFirestore:(std::shared_ptr)firestore +- (instancetype)initWithFirestore:(FIRFirestore *)firestore documentKey:(model::DocumentKey)documentKey document:(const absl::optional &)document fromCache:(bool)fromCache diff --git a/Firestore/Source/API/FIRDocumentSnapshot.mm b/Firestore/Source/API/FIRDocumentSnapshot.mm index b6ffbf886fc..2615ed43a2f 100644 --- a/Firestore/Source/API/FIRDocumentSnapshot.mm +++ b/Firestore/Source/API/FIRDocumentSnapshot.mm @@ -27,8 +27,10 @@ #import "Firestore/Source/API/FIRGeoPoint+Internal.h" #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" #import "Firestore/Source/API/FIRTimestamp+Internal.h" +#import "Firestore/Source/API/FSTUserDataWriter.h" #import "Firestore/Source/API/converters.h" +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/api/document_reference.h" #include "Firestore/core/src/api/document_snapshot.h" #include "Firestore/core/src/api/firestore.h" @@ -36,15 +38,15 @@ #include "Firestore/core/src/model/database_id.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/model/field_value_options.h" #include "Firestore/core/src/nanopb/nanopb_util.h" +#include "Firestore/core/src/remote/serializer.h" #include "Firestore/core/src/util/exception.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/log.h" #include "Firestore/core/src/util/string_apple.h" namespace util = firebase::firestore::util; +using firebase::firestore::google_firestore_v1_Value; using firebase::firestore::api::DocumentSnapshot; using firebase::firestore::api::Firestore; using firebase::firestore::api::MakeFIRGeoPoint; @@ -54,65 +56,47 @@ using firebase::firestore::model::Document; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldPath; -using firebase::firestore::model::FieldValue; -using firebase::firestore::model::FieldValueOptions; using firebase::firestore::model::ObjectValue; -using firebase::firestore::model::ServerTimestampBehavior; +using firebase::firestore::remote::Serializer; using firebase::firestore::nanopb::MakeNSData; using firebase::firestore::util::MakeString; using firebase::firestore::util::ThrowInvalidArgument; +using firebase::firestore::google_firestore_v1_Value; NS_ASSUME_NONNULL_BEGIN -namespace { - -/** - * Converts a public FIRServerTimestampBehavior into its internal equivalent. - */ -ServerTimestampBehavior InternalServerTimestampBehavior(FIRServerTimestampBehavior behavior) { - switch (behavior) { - case FIRServerTimestampBehaviorNone: - return ServerTimestampBehavior::kNone; - case FIRServerTimestampBehaviorEstimate: - return ServerTimestampBehavior::kEstimate; - case FIRServerTimestampBehaviorPrevious: - return ServerTimestampBehavior::kPrevious; - default: - HARD_FAIL("Unexpected server timestamp option: %s", behavior); - } -} - -} // namespace - @implementation FIRDocumentSnapshot { DocumentSnapshot _snapshot; + std::unique_ptr _serializer; FIRSnapshotMetadata *_cachedMetadata; } - (instancetype)initWithSnapshot:(DocumentSnapshot &&)snapshot { if (self = [super init]) { _snapshot = std::move(snapshot); + _serializer.reset(new Serializer(_snapshot.firestore()->database_id())); } return self; } -- (instancetype)initWithFirestore:(std::shared_ptr)firestore +- (instancetype)initWithFirestore:(FIRFirestore *)firestore documentKey:(DocumentKey)documentKey document:(const absl::optional &)document metadata:(SnapshotMetadata)metadata { DocumentSnapshot wrapped; if (document.has_value()) { wrapped = - DocumentSnapshot::FromDocument(std::move(firestore), document.value(), std::move(metadata)); + DocumentSnapshot::FromDocument(firestore.wrapped, document.value(), std::move(metadata)); } else { - wrapped = DocumentSnapshot::FromNoDocument(std::move(firestore), std::move(documentKey), + wrapped = DocumentSnapshot::FromNoDocument(firestore.wrapped, std::move(documentKey), std::move(metadata)); } + _serializer.reset(new Serializer(firestore.databaseID)); return [self initWithSnapshot:std::move(wrapped)]; } -- (instancetype)initWithFirestore:(std::shared_ptr)firestore +- (instancetype)initWithFirestore:(FIRFirestore *)firestore documentKey:(DocumentKey)documentKey document:(const absl::optional &)document fromCache:(bool)fromCache @@ -169,11 +153,13 @@ - (FIRSnapshotMetadata *)metadata { - (nullable NSDictionary *)dataWithServerTimestampBehavior: (FIRServerTimestampBehavior)serverTimestampBehavior { - FieldValueOptions options = [self optionsForServerTimestampBehavior:serverTimestampBehavior]; - absl::optional data = _snapshot.GetData(); + absl::optional data = _snapshot.GetValue(FieldPath::EmptyPath()); if (!data) return nil; - return [self convertedObject:data->GetInternalValue() options:options]; + FSTUserDataWriter *dataWriter = + [[FSTUserDataWriter alloc] initWithFirestore:_snapshot.firestore() + serverTimestampBehavior:serverTimestampBehavior]; + return [dataWriter convertedValue:*data]; } - (nullable id)valueForField:(id)field { @@ -190,110 +176,18 @@ - (nullable id)valueForField:(id)field } else { ThrowInvalidArgument("Subscript key must be an NSString or FIRFieldPath."); } - - absl::optional fieldValue = _snapshot.GetValue(fieldPath); - FieldValueOptions options = [self optionsForServerTimestampBehavior:serverTimestampBehavior]; - return !fieldValue ? nil : [self convertedValue:*fieldValue options:options]; + absl::optional fieldValue = _snapshot.GetValue(fieldPath); + if (!fieldValue) return nil; + FSTUserDataWriter *dataWriter = + [[FSTUserDataWriter alloc] initWithFirestore:_snapshot.firestore() + serverTimestampBehavior:serverTimestampBehavior]; + return [dataWriter convertedValue:*fieldValue]; } - (nullable id)objectForKeyedSubscript:(id)key { return [self valueForField:key]; } -- (FieldValueOptions)optionsForServerTimestampBehavior: - (FIRServerTimestampBehavior)serverTimestampBehavior { - return FieldValueOptions(InternalServerTimestampBehavior(serverTimestampBehavior)); -} - -- (id)convertedValue:(FieldValue)value options:(const FieldValueOptions &)options { - switch (value.type()) { - case FieldValue::Type::Null: - return [NSNull null]; - case FieldValue::Type::Boolean: - return value.boolean_value() ? @YES : @NO; - case FieldValue::Type::Integer: - return @(value.integer_value()); - case FieldValue::Type::Double: - return @(value.double_value()); - case FieldValue::Type::Timestamp: - return [self convertedTimestamp:value]; - case FieldValue::Type::ServerTimestamp: - return [self convertedServerTimestamp:value options:options]; - case FieldValue::Type::String: - return util::MakeNSString(value.string_value()); - case FieldValue::Type::Blob: - return MakeNSData(value.blob_value()); - case FieldValue::Type::Reference: - return [self convertedReference:value]; - case FieldValue::Type::GeoPoint: - return MakeFIRGeoPoint(value.geo_point_value()); - case FieldValue::Type::Array: - return [self convertedArray:value.array_value() options:options]; - case FieldValue::Type::Object: - return [self convertedObject:value.object_value() options:options]; - } - - UNREACHABLE(); -} - -- (id)convertedTimestamp:(const FieldValue &)value { - return MakeFIRTimestamp(value.timestamp_value()); -} - -- (id)convertedServerTimestamp:(const FieldValue &)value - options:(const FieldValueOptions &)options { - const auto &sts = value.server_timestamp_value(); - switch (options.server_timestamp_behavior()) { - case ServerTimestampBehavior::kNone: - return [NSNull null]; - case ServerTimestampBehavior::kEstimate: { - FieldValue local_write_time = FieldValue::FromTimestamp(sts.local_write_time()); - return [self convertedTimestamp:local_write_time]; - } - case ServerTimestampBehavior::kPrevious: - return sts.previous_value() ? [self convertedValue:*sts.previous_value() options:options] - : [NSNull null]; - } - - UNREACHABLE(); -} - -- (id)convertedReference:(const FieldValue &)value { - const auto &ref = value.reference_value(); - const DatabaseId &refDatabase = ref.database_id(); - const DatabaseId &database = _snapshot.firestore()->database_id(); - if (refDatabase != database) { - LOG_WARN("Document %s contains a document reference within a different database (%s/%s) which " - "is not supported. It will be treated as a reference within the current database " - "(%s/%s) instead.", - _snapshot.CreateReference().Path(), refDatabase.project_id(), - refDatabase.database_id(), database.project_id(), database.database_id()); - } - const DocumentKey &key = ref.key(); - return [[FIRDocumentReference alloc] initWithKey:key firestore:_snapshot.firestore()]; -} - -- (NSArray *)convertedArray:(const FieldValue::Array &)arrayContents - options:(const FieldValueOptions &)options { - NSMutableArray *result = [NSMutableArray arrayWithCapacity:arrayContents.size()]; - for (const FieldValue &value : arrayContents) { - [result addObject:[self convertedValue:value options:options]]; - } - return result; -} - -- (NSDictionary *)convertedObject:(const FieldValue::Map &)objectValue - options:(const FieldValueOptions &)options { - NSMutableDictionary *result = [NSMutableDictionary dictionary]; - for (const auto &kv : objectValue) { - const std::string &key = kv.first; - const FieldValue &value = kv.second; - - result[util::MakeNSString(key)] = [self convertedValue:value options:options]; - } - return result; -} - @end @implementation FIRQueryDocumentSnapshot diff --git a/Firestore/Source/API/FIRFirestore+Internal.h b/Firestore/Source/API/FIRFirestore+Internal.h index 018f75fb696..0e74f72ded4 100644 --- a/Firestore/Source/API/FIRFirestore+Internal.h +++ b/Firestore/Source/API/FIRFirestore+Internal.h @@ -25,7 +25,7 @@ @class FIRApp; @class FSTFirestoreClient; -@class FSTUserDataConverter; +@class FSTUserDataReader; namespace firebase { namespace firestore { @@ -79,7 +79,7 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, assign, readonly) std::shared_ptr wrapped; @property(nonatomic, assign, readonly) const model::DatabaseId &databaseID; -@property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter; +@property(nonatomic, strong, readonly) FSTUserDataReader *dataReader; @end diff --git a/Firestore/Source/API/FIRFirestore.mm b/Firestore/Source/API/FIRFirestore.mm index ba48a23c3da..f249f26aebe 100644 --- a/Firestore/Source/API/FIRFirestore.mm +++ b/Firestore/Source/API/FIRFirestore.mm @@ -31,7 +31,7 @@ #import "Firestore/Source/API/FIRTransaction+Internal.h" #import "Firestore/Source/API/FIRWriteBatch+Internal.h" #import "Firestore/Source/API/FSTFirestoreComponent.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/api/collection_reference.h" #include "Firestore/core/src/api/document_reference.h" @@ -89,7 +89,7 @@ @interface FIRFirestore () -@property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter; +@property(nonatomic, strong, readonly) FSTUserDataReader *dataReader; @end @@ -162,8 +162,8 @@ - (instancetype)initWithDatabaseID:(model::DatabaseId)databaseID } }; - _dataConverter = [[FSTUserDataConverter alloc] initWithDatabaseID:_firestore->database_id() - preConverter:block]; + _dataReader = [[FSTUserDataReader alloc] initWithDatabaseID:_firestore->database_id() + preConverter:block]; // Use the property setter so the default settings get plumbed into _firestoreClient. self.settings = [[FIRFirestoreSettings alloc] init]; } @@ -241,8 +241,7 @@ - (FIRQuery *)collectionGroupWithID:(NSString *)collectionID { } - (FIRWriteBatch *)batch { - return [FIRWriteBatch writeBatchWithDataConverter:self.dataConverter - writeBatch:_firestore->GetBatch()]; + return [FIRWriteBatch writeBatchWithDataReader:self.dataReader writeBatch:_firestore->GetBatch()]; } - (void)runTransactionWithBlock:(UserUpdateBlock)updateBlock diff --git a/Firestore/Source/API/FIRQuery.mm b/Firestore/Source/API/FIRQuery.mm index f38df848eb7..dd3b938a8b2 100644 --- a/Firestore/Source/API/FIRQuery.mm +++ b/Firestore/Source/API/FIRQuery.mm @@ -32,7 +32,7 @@ #import "Firestore/Source/API/FIRQuery+Internal.h" #import "Firestore/Source/API/FIRQuerySnapshot+Internal.h" #import "Firestore/Source/API/FIRSnapshotMetadata+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/api/query_core.h" #include "Firestore/core/src/api/query_listener_registration.h" @@ -47,8 +47,11 @@ #include "Firestore/core/src/core/query.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/field_value.h" #include "Firestore/core/src/model/resource_path.h" +#include "Firestore/core/src/model/server_timestamp_util.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/nanopb/message.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" #include "Firestore/core/src/util/error_apple.h" #include "Firestore/core/src/util/exception.h" #include "Firestore/core/src/util/hard_assert.h" @@ -58,6 +61,7 @@ #include "absl/strings/match.h" namespace util = firebase::firestore::util; +namespace nanopb = firebase::firestore::nanopb; using firebase::firestore::api::Firestore; using firebase::firestore::api::Query; using firebase::firestore::api::QueryListenerRegistration; @@ -75,12 +79,25 @@ using firebase::firestore::core::OrderByList; using firebase::firestore::core::QueryListener; using firebase::firestore::core::ViewSnapshot; +using firebase::firestore::google_firestore_v1_ArrayValue; +using firebase::firestore::google_firestore_v1_Value; +using firebase::firestore::google_firestore_v1_Value_fields; using firebase::firestore::model::DatabaseId; +using firebase::firestore::model::DeepClone; using firebase::firestore::model::Document; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldPath; -using firebase::firestore::model::FieldValue; +using firebase::firestore::model::GetTypeOrder; +using firebase::firestore::model::IsServerTimestamp; +using firebase::firestore::model::RefValue; using firebase::firestore::model::ResourcePath; +using firebase::firestore::model::TypeOrder; +using firebase::firestore::nanopb::CheckedSize; +using firebase::firestore::nanopb::MakeArray; +using firebase::firestore::nanopb::MakeString; +using firebase::firestore::nanopb::Message; +using firebase::firestore::nanopb::SharedMessage; +using firebase::firestore::nanopb::MakeSharedMessage; using firebase::firestore::util::MakeNSError; using firebase::firestore::util::MakeString; using firebase::firestore::util::StatusOr; @@ -473,12 +490,12 @@ - (FIRQuery *)queryEndingAtValues:(NSArray *)fieldValues { #pragma mark - Private Methods -- (FieldValue)parsedQueryValue:(id)value { - return [self.firestore.dataConverter parsedQueryValue:value]; +- (Message)parsedQueryValue:(id)value { + return [self.firestore.dataReader parsedQueryValue:value]; } -- (FieldValue)parsedQueryValue:(id)value allowArrays:(bool)allowArrays { - return [self.firestore.dataConverter parsedQueryValue:value allowArrays:allowArrays]; +- (Message)parsedQueryValue:(id)value allowArrays:(bool)allowArrays { + return [self.firestore.dataReader parsedQueryValue:value allowArrays:allowArrays]; } - (QuerySnapshotListener)wrapQuerySnapshotBlock:(FIRQuerySnapshotBlock)block { @@ -514,9 +531,10 @@ - (FIRQuery *)queryWithFilterOperator:(Filter::Operator)filterOperator - (FIRQuery *)queryWithFilterOperator:(Filter::Operator)filterOperator path:(const FieldPath &)fieldPath value:(id)value { - FieldValue fieldValue = [self parsedQueryValue:value - allowArrays:filterOperator == Filter::Operator::In || - filterOperator == Filter::Operator::NotIn]; + Message fieldValue = + [self parsedQueryValue:value + allowArrays:filterOperator == Filter::Operator::In || + filterOperator == Filter::Operator::NotIn]; auto describer = [value] { return MakeString(NSStringFromClass([value class])); }; return Wrap(_query.Filter(fieldPath, filterOperator, std::move(fieldValue), describer)); } @@ -538,38 +556,42 @@ - (Bound)boundFromSnapshot:(FIRDocumentSnapshot *)snapshot isBefore:(BOOL)isBefo } const Document &document = *snapshot.internalDocument; const DatabaseId &databaseID = self.firestore.databaseID; - std::vector components; + const OrderByList &order_bys = self.query.order_bys(); + + SharedMessage components{{}}; + components->values_count = CheckedSize(order_bys.size()); + components->values = MakeArray(components->values_count); // Because people expect to continue/end a query at the exact document provided, we need to // use the implicit sort order rather than the explicit sort order, because it's guaranteed to // contain the document key. That way the position becomes unambiguous and the query // continues/ends exactly at the provided document. Without the key (by using the explicit sort // orders), multiple documents could match the position, yielding duplicate results. - for (const OrderBy &orderBy : self.query.order_bys()) { - if (orderBy.field() == FieldPath::KeyFieldPath()) { - components.push_back(FieldValue::FromReference(databaseID, document.key())); + for (size_t i = 0; i < order_bys.size(); ++i) { + if (order_bys[i].field() == FieldPath::KeyFieldPath()) { + components->values[i] = *RefValue(databaseID, document->key()).release(); } else { - absl::optional value = document.field(orderBy.field()); + absl::optional value = document->field(order_bys[i].field()); if (value) { - if (value->type() == FieldValue::Type::ServerTimestamp) { + if (IsServerTimestamp(*value)) { ThrowInvalidArgument( "Invalid query. You are trying to start or end a query using a document for which " "the field '%s' is an uncommitted server timestamp. (Since the value of this field " "is unknown, you cannot start/end a query with it.)", - orderBy.field().CanonicalString()); + order_bys[i].field().CanonicalString()); } else { - components.push_back(*value); + components->values[i] = *DeepClone(*value).release(); } } else { ThrowInvalidArgument( "Invalid query. You are trying to start or end a query using a document for which the " "field '%s' (used as the order by) does not exist.", - orderBy.field().CanonicalString()); + order_bys[i].field().CanonicalString()); } } } - return Bound(std::move(components), isBefore); + return Bound::FromValue(std::move(components), isBefore); } /** Converts a list of field values to an Bound. */ @@ -581,17 +603,20 @@ - (Bound)boundFromFieldValues:(NSArray *)fieldValues isBefore:(BOOL)isBefore "than were specified in the order by."); } - std::vector components; + SharedMessage components{{}}; + components->values_count = CheckedSize(fieldValues.count); + components->values = MakeArray(components->values_count); for (NSUInteger idx = 0, max = fieldValues.count; idx < max; ++idx) { id rawValue = fieldValues[idx]; const OrderBy &sortOrder = explicitSortOrders[idx]; - FieldValue fieldValue = [self parsedQueryValue:rawValue]; + Message fieldValue{[self parsedQueryValue:rawValue]}; if (sortOrder.field().IsKeyFieldPath()) { - if (fieldValue.type() != FieldValue::Type::String) { + if (GetTypeOrder(*fieldValue) != TypeOrder::kString) { ThrowInvalidArgument("Invalid query. Expected a string for the document ID."); } - const std::string &documentID = fieldValue.string_value(); + + std::string documentID = MakeString(fieldValue->string_value); if (!self.query.IsCollectionGroupQuery() && absl::StrContains(documentID, "/")) { ThrowInvalidArgument("Invalid query. When querying a collection and ordering by document " "ID, you must pass a plain document ID, but '%s' contains a slash.", @@ -605,13 +630,13 @@ - (Bound)boundFromFieldValues:(NSArray *)fieldValues isBefore:(BOOL)isBefore path.CanonicalString()); } DocumentKey key{path}; - fieldValue = FieldValue::FromReference(self.firestore.databaseID, key); + components->values[idx] = *RefValue(self.firestore.databaseID, key).release(); + } else { + components->values[idx] = *fieldValue.release(); } - - components.push_back(fieldValue); } - return Bound(std::move(components), isBefore); + return Bound::FromValue(std::move(components), isBefore); } @end diff --git a/Firestore/Source/API/FIRTransaction.mm b/Firestore/Source/API/FIRTransaction.mm index 82f503107fe..42b4203c26a 100644 --- a/Firestore/Source/API/FIRTransaction.mm +++ b/Firestore/Source/API/FIRTransaction.mm @@ -24,7 +24,7 @@ #import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRTransaction+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/core/transaction.h" #include "Firestore/core/src/core/user_data.h" @@ -39,7 +39,6 @@ using firebase::firestore::core::ParsedUpdateData; using firebase::firestore::core::Transaction; using firebase::firestore::model::Document; -using firebase::firestore::model::MaybeDocument; using firebase::firestore::util::MakeNSError; using firebase::firestore::util::StatusOr; using firebase::firestore::util::ThrowInvalidArgument; @@ -88,8 +87,8 @@ - (FIRTransaction *)setData:(NSDictionary *)data forDocument:(FIRDocumentReference *)document merge:(BOOL)merge { [self validateReference:document]; - ParsedSetData parsed = merge ? [self.firestore.dataConverter parsedMergeData:data fieldMask:nil] - : [self.firestore.dataConverter parsedSetData:data]; + ParsedSetData parsed = merge ? [self.firestore.dataReader parsedMergeData:data fieldMask:nil] + : [self.firestore.dataReader parsedSetData:data]; _internalTransaction->Set(document.key, std::move(parsed)); return self; } @@ -98,7 +97,7 @@ - (FIRTransaction *)setData:(NSDictionary *)data forDocument:(FIRDocumentReference *)document mergeFields:(NSArray *)mergeFields { [self validateReference:document]; - ParsedSetData parsed = [self.firestore.dataConverter parsedMergeData:data fieldMask:mergeFields]; + ParsedSetData parsed = [self.firestore.dataReader parsedMergeData:data fieldMask:mergeFields]; _internalTransaction->Set(document.key, std::move(parsed)); return self; } @@ -106,7 +105,7 @@ - (FIRTransaction *)setData:(NSDictionary *)data - (FIRTransaction *)updateData:(NSDictionary *)fields forDocument:(FIRDocumentReference *)document { [self validateReference:document]; - ParsedUpdateData parsed = [self.firestore.dataConverter parsedUpdateData:fields]; + ParsedUpdateData parsed = [self.firestore.dataReader parsedUpdateData:fields]; _internalTransaction->Update(document.key, std::move(parsed)); return self; } @@ -123,7 +122,7 @@ - (void)getDocument:(FIRDocumentReference *)document [self validateReference:document]; _internalTransaction->Lookup( {document.key}, - [self, document, completion](const StatusOr> &maybe_documents) { + [self, document, completion](const StatusOr> &maybe_documents) { if (!maybe_documents.ok()) { completion(nil, MakeNSError(maybe_documents.status())); return; @@ -131,26 +130,25 @@ - (void)getDocument:(FIRDocumentReference *)document const auto &documents = maybe_documents.ValueOrDie(); HARD_ASSERT(documents.size() == 1, "Mismatch in docs returned from document lookup."); - const MaybeDocument &internalDoc = documents.front(); - if (internalDoc.is_no_document()) { + const Document &internalDoc = documents.front(); + if (internalDoc->is_found_document()) { FIRDocumentSnapshot *doc = - [[FIRDocumentSnapshot alloc] initWithFirestore:self.firestore.wrapped - documentKey:document.key - document:absl::nullopt + [[FIRDocumentSnapshot alloc] initWithFirestore:self.firestore + documentKey:internalDoc->key() + document:internalDoc fromCache:false hasPendingWrites:false]; completion(doc, nil); - } else if (internalDoc.is_document()) { - FIRDocumentSnapshot *doc = - [[FIRDocumentSnapshot alloc] initWithFirestore:self.firestore.wrapped - documentKey:internalDoc.key() - document:Document(internalDoc) - fromCache:false - hasPendingWrites:false]; + } else if (internalDoc->is_no_document()) { + FIRDocumentSnapshot *doc = [[FIRDocumentSnapshot alloc] initWithFirestore:self.firestore + documentKey:document.key + document:absl::nullopt + fromCache:false + hasPendingWrites:false]; completion(doc, nil); } else { HARD_FAIL("BatchGetDocumentsRequest returned unexpected document type: %s", - internalDoc.type()); + internalDoc.ToString()); } }); } diff --git a/Firestore/Source/API/FIRWriteBatch+Internal.h b/Firestore/Source/API/FIRWriteBatch+Internal.h index ffd297e7738..54329f53e1a 100644 --- a/Firestore/Source/API/FIRWriteBatch+Internal.h +++ b/Firestore/Source/API/FIRWriteBatch+Internal.h @@ -20,7 +20,7 @@ #include "Firestore/core/src/api/write_batch.h" -@class FSTUserDataConverter; +@class FSTUserDataReader; namespace api = firebase::firestore::api; @@ -28,8 +28,8 @@ NS_ASSUME_NONNULL_BEGIN @interface FIRWriteBatch (Internal) -+ (instancetype)writeBatchWithDataConverter:(FSTUserDataConverter *)dataConverter - writeBatch:(api::WriteBatch &&)writeBatch; ++ (instancetype)writeBatchWithDataReader:(FSTUserDataReader *)dataReader + writeBatch:(api::WriteBatch &&)writeBatch; @end diff --git a/Firestore/Source/API/FIRWriteBatch.mm b/Firestore/Source/API/FIRWriteBatch.mm index 1565177ef34..048f1802dca 100644 --- a/Firestore/Source/API/FIRWriteBatch.mm +++ b/Firestore/Source/API/FIRWriteBatch.mm @@ -17,7 +17,7 @@ #import "Firestore/Source/API/FIRWriteBatch+Internal.h" #import "Firestore/Source/API/FIRDocumentReference+Internal.h" -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include "Firestore/core/src/api/write_batch.h" #include "Firestore/core/src/core/user_data.h" @@ -34,19 +34,18 @@ @interface FIRWriteBatch () -- (instancetype)initWithDataConverter:(FSTUserDataConverter *)dataConverter - writeBatch:(api::WriteBatch &&)writeBatch NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithDataReader:(FSTUserDataReader *)dataReader + writeBatch:(api::WriteBatch &&)writeBatch NS_DESIGNATED_INITIALIZER; -@property(nonatomic, strong, readonly) FSTUserDataConverter *dataConverter; +@property(nonatomic, strong, readonly) FSTUserDataReader *dataReader; @end @implementation FIRWriteBatch (Internal) -+ (instancetype)writeBatchWithDataConverter:(FSTUserDataConverter *)dataConverter - writeBatch:(api::WriteBatch &&)writeBatch { - return [[FIRWriteBatch alloc] initWithDataConverter:dataConverter - writeBatch:std::move(writeBatch)]; ++ (instancetype)writeBatchWithDataReader:(FSTUserDataReader *)dataReader + writeBatch:(api::WriteBatch &&)writeBatch { + return [[FIRWriteBatch alloc] initWithDataReader:dataReader writeBatch:std::move(writeBatch)]; } @end @@ -55,11 +54,11 @@ @implementation FIRWriteBatch { util::DelayedConstructor _writeBatch; } -- (instancetype)initWithDataConverter:(FSTUserDataConverter *)dataConverter - writeBatch:(api::WriteBatch &&)writeBatch { +- (instancetype)initWithDataReader:(FSTUserDataReader *)dataReader + writeBatch:(api::WriteBatch &&)writeBatch { self = [super init]; if (self) { - _dataConverter = dataConverter; + _dataReader = dataReader; _writeBatch.Init(std::move(writeBatch)); } return self; @@ -73,8 +72,8 @@ - (FIRWriteBatch *)setData:(NSDictionary *)data - (FIRWriteBatch *)setData:(NSDictionary *)data forDocument:(FIRDocumentReference *)document merge:(BOOL)merge { - ParsedSetData parsed = merge ? [self.dataConverter parsedMergeData:data fieldMask:nil] - : [self.dataConverter parsedSetData:data]; + ParsedSetData parsed = merge ? [self.dataReader parsedMergeData:data fieldMask:nil] + : [self.dataReader parsedSetData:data]; _writeBatch->SetData(document.internalReference, std::move(parsed)); return self; @@ -83,7 +82,7 @@ - (FIRWriteBatch *)setData:(NSDictionary *)data - (FIRWriteBatch *)setData:(NSDictionary *)data forDocument:(FIRDocumentReference *)document mergeFields:(NSArray *)mergeFields { - ParsedSetData parsed = [self.dataConverter parsedMergeData:data fieldMask:mergeFields]; + ParsedSetData parsed = [self.dataReader parsedMergeData:data fieldMask:mergeFields]; _writeBatch->SetData(document.internalReference, std::move(parsed)); return self; @@ -91,7 +90,7 @@ - (FIRWriteBatch *)setData:(NSDictionary *)data - (FIRWriteBatch *)updateData:(NSDictionary *)fields forDocument:(FIRDocumentReference *)document { - ParsedUpdateData parsed = [self.dataConverter parsedUpdateData:fields]; + ParsedUpdateData parsed = [self.dataReader parsedUpdateData:fields]; _writeBatch->UpdateData(document.internalReference, std::move(parsed)); return self; diff --git a/Firestore/Source/API/FSTFirestoreComponent.mm b/Firestore/Source/API/FSTFirestoreComponent.mm index 028a73d7b8f..faaf25f45ab 100644 --- a/Firestore/Source/API/FSTFirestoreComponent.mm +++ b/Firestore/Source/API/FSTFirestoreComponent.mm @@ -28,7 +28,6 @@ #include "Firestore/core/src/api/firestore.h" #include "Firestore/core/src/auth/credentials_provider.h" #include "Firestore/core/src/auth/firebase_credentials_provider_apple.h" -#include "Firestore/core/src/model/maybe_document.h" #include "Firestore/core/src/remote/firebase_metadata_provider.h" #include "Firestore/core/src/remote/firebase_metadata_provider_apple.h" #include "Firestore/core/src/util/async_queue.h" diff --git a/Firestore/Source/API/FSTUserDataConverter.h b/Firestore/Source/API/FSTUserDataReader.h similarity index 82% rename from Firestore/Source/API/FSTUserDataConverter.h rename to Firestore/Source/API/FSTUserDataReader.h index 7c4ed0119eb..f348ff70b82 100644 --- a/Firestore/Source/API/FSTUserDataConverter.h +++ b/Firestore/Source/API/FSTUserDataReader.h @@ -1,5 +1,5 @@ /* - * Copyright 2017 Google + * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,14 +18,17 @@ #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/core_fwd.h" #include "Firestore/core/src/model/database_id.h" #include "Firestore/core/src/model/model_fwd.h" +#include "Firestore/core/src/nanopb/message.h" @class FIRTimestamp; namespace core = firebase::firestore::core; namespace model = firebase::firestore::model; +namespace nanopb = firebase::firestore::nanopb; NS_ASSUME_NONNULL_BEGIN @@ -59,7 +62,7 @@ typedef id _Nullable (^FSTPreConverterBlock)(id _Nullable); /** * Helper for parsing raw user input (provided via the API) into internal model classes. */ -@interface FSTUserDataConverter : NSObject +@interface FSTUserDataReader : NSObject - (instancetype)init NS_UNAVAILABLE; - (instancetype)initWithDatabaseID:(model::DatabaseId)databaseID @@ -75,7 +78,7 @@ typedef id _Nullable (^FSTPreConverterBlock)(id _Nullable); - (core::ParsedUpdateData)parsedUpdateData:(id)input; /** Parse a "query value" (e.g. value in a where filter or a value in a cursor bound). */ -- (model::FieldValue)parsedQueryValue:(id)input; +- (nanopb::Message)parsedQueryValue:(id)input; /** * Parse a "query value" (e.g. value in a where filter or a value in a cursor bound). @@ -83,7 +86,9 @@ typedef id _Nullable (^FSTPreConverterBlock)(id _Nullable); * @param allowArrays Whether the query value is an array that may directly contain additional * arrays (e.g.) the operand of an `in` query). */ -- (model::FieldValue)parsedQueryValue:(id)input allowArrays:(bool)allowArrays; +- (nanopb::Message)parsedQueryValue:(id)input + allowArrays: + (bool)allowArrays; @end diff --git a/Firestore/Source/API/FSTUserDataConverter.mm b/Firestore/Source/API/FSTUserDataReader.mm similarity index 64% rename from Firestore/Source/API/FSTUserDataConverter.mm rename to Firestore/Source/API/FSTUserDataReader.mm index 3c2d69030b3..d4e0063633e 100644 --- a/Firestore/Source/API/FSTUserDataConverter.mm +++ b/Firestore/Source/API/FSTUserDataReader.mm @@ -14,7 +14,7 @@ * limitations under the License. */ -#import "Firestore/Source/API/FSTUserDataConverter.h" +#import "Firestore/Source/API/FSTUserDataReader.h" #include #include @@ -31,6 +31,7 @@ #import "Firestore/Source/API/FIRFirestore+Internal.h" #import "Firestore/Source/API/FIRGeoPoint+Internal.h" #import "Firestore/Source/API/converters.h" +#import "Firestore/core/include/firebase/firestore/geo_point.h" #include "Firestore/core/src/core/user_data.h" #include "Firestore/core/src/model/database_id.h" @@ -38,21 +39,29 @@ #include "Firestore/core/src/model/field_mask.h" #include "Firestore/core/src/model/field_path.h" #include "Firestore/core/src/model/field_transform.h" -#include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/model/object_value.h" #include "Firestore/core/src/model/precondition.h" +#include "Firestore/core/src/model/resource_path.h" #include "Firestore/core/src/model/transform_operation.h" +#include "Firestore/core/src/model/value_util.h" #include "Firestore/core/src/nanopb/nanopb_util.h" +#include "Firestore/core/src/nanopb/reader.h" +#include "Firestore/core/src/remote/serializer.h" #include "Firestore/core/src/timestamp_internal.h" #include "Firestore/core/src/util/exception.h" #include "Firestore/core/src/util/hard_assert.h" +#include "Firestore/core/src/util/read_context.h" #include "Firestore/core/src/util/string_apple.h" + #include "absl/memory/memory.h" #include "absl/strings/match.h" #include "absl/types/optional.h" namespace util = firebase::firestore::util; +namespace nanopb = firebase::firestore::nanopb; using firebase::Timestamp; using firebase::TimestampInternal; +using firebase::firestore::GeoPoint; using firebase::firestore::core::ParseAccumulator; using firebase::firestore::core::ParseContext; using firebase::firestore::core::ParsedSetData; @@ -63,13 +72,26 @@ using firebase::firestore::model::DocumentKey; using firebase::firestore::model::FieldMask; using firebase::firestore::model::FieldPath; -using firebase::firestore::model::FieldValue; +using firebase::firestore::model::FieldTransform; +using firebase::firestore::model::NullValue; using firebase::firestore::model::NumericIncrementTransform; using firebase::firestore::model::ObjectValue; +using firebase::firestore::model::ResourcePath; using firebase::firestore::model::ServerTimestampTransform; using firebase::firestore::model::TransformOperation; -using firebase::firestore::nanopb::MakeByteString; +using firebase::firestore::nanopb::CheckedSize; +using firebase::firestore::nanopb::Message; +using firebase::firestore::remote::Serializer; using firebase::firestore::util::ThrowInvalidArgument; +using firebase::firestore::util::ReadContext; +using firebase::firestore::google_firestore_v1_Value; +using firebase::firestore::google_firestore_v1_MapValue; +using firebase::firestore::google_firestore_v1_ArrayValue; +using firebase::firestore::google_protobuf_NullValue_NULL_VALUE; +using firebase::firestore::google_firestore_v1_MapValue_FieldsEntry; +using firebase::firestore::google_type_LatLng; +using firebase::firestore::google_protobuf_Timestamp; +using nanopb::StringReader; NS_ASSUME_NONNULL_BEGIN @@ -99,15 +121,13 @@ - (instancetype)initWithKey:(DocumentKey)key databaseID:(DatabaseId)databaseID { @end -#pragma mark - Conversion helpers - -#pragma mark - FSTUserDataConverter +#pragma mark - FSTUserDataReader -@interface FSTUserDataConverter () +@interface FSTUserDataReader () @property(strong, nonatomic, readonly) FSTPreConverterBlock preConverter; @end -@implementation FSTUserDataConverter { +@implementation FSTUserDataReader { DatabaseId _databaseID; } @@ -129,10 +149,10 @@ - (ParsedSetData)parsedSetData:(id)input { } ParseAccumulator accumulator{UserDataSource::Set}; - absl::optional updateData = [self parseData:input context:accumulator.RootContext()]; + auto updateData = [self parseData:input context:accumulator.RootContext()]; HARD_ASSERT(updateData.has_value(), "Parsed data should not be nil."); - return std::move(accumulator).SetData(ObjectValue(std::move(*updateData))); + return std::move(accumulator).SetData(ObjectValue{std::move(*updateData)}); } - (ParsedSetData)parsedMergeData:(id)input fieldMask:(nullable NSArray *)fieldMask { @@ -144,10 +164,10 @@ - (ParsedSetData)parsedMergeData:(id)input fieldMask:(nullable NSArray *)fie ParseAccumulator accumulator{UserDataSource::MergeSet}; - absl::optional updateData = [self parseData:input context:accumulator.RootContext()]; + auto updateData = [self parseData:input context:accumulator.RootContext()]; HARD_ASSERT(updateData.has_value(), "Parsed data should not be nil."); - ObjectValue updateObject = ObjectValue(std::move(*updateData)); + ObjectValue updateObject{std::move(*updateData)}; if (fieldMask) { std::set validatedFieldPaths; @@ -173,10 +193,10 @@ - (ParsedSetData)parsedMergeData:(id)input fieldMask:(nullable NSArray *)fie } return std::move(accumulator) - .MergeData(updateObject, FieldMask{std::move(validatedFieldPaths)}); + .MergeData(std::move(updateObject), FieldMask{std::move(validatedFieldPaths)}); } else { - return std::move(accumulator).MergeData(updateObject); + return std::move(accumulator).MergeData(std::move(updateObject)); } } @@ -191,7 +211,7 @@ - (ParsedUpdateData)parsedUpdateData:(id)input { ParseAccumulator accumulator{UserDataSource::Update}; __block ParseContext context = accumulator.RootContext(); - __block ObjectValue updateData = ObjectValue::Empty(); + __block ObjectValue updateData; [dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *) { FieldPath path; @@ -209,31 +229,30 @@ - (ParsedUpdateData)parsedUpdateData:(id)input { // Add it to the field mask, but don't add anything to updateData. context.AddToFieldMask(std::move(path)); } else { - absl::optional parsedValue = [self parseData:value - context:context.ChildContext(path)]; + auto parsedValue = [self parseData:value context:context.ChildContext(path)]; if (parsedValue) { context.AddToFieldMask(path); - updateData = updateData.Set(path, *parsedValue); + updateData.Set(path, std::move(*parsedValue)); } } }]; - return std::move(accumulator).UpdateData(updateData); + return std::move(accumulator).UpdateData(std::move(updateData)); } -- (FieldValue)parsedQueryValue:(id)input { +- (Message)parsedQueryValue:(id)input { return [self parsedQueryValue:input allowArrays:false]; } -- (FieldValue)parsedQueryValue:(id)input allowArrays:(bool)allowArrays { +- (Message)parsedQueryValue:(id)input allowArrays:(bool)allowArrays { ParseAccumulator accumulator{allowArrays ? UserDataSource::ArrayArgument : UserDataSource::Argument}; - absl::optional parsed = [self parseData:input context:accumulator.RootContext()]; + auto parsed = [self parseData:input context:accumulator.RootContext()]; HARD_ASSERT(parsed, "Parsed data should not be nil."); HARD_ASSERT(accumulator.field_transforms().empty(), "Field transforms should have been disallowed."); - return *parsed; + return std::move(*parsed); } /** @@ -246,7 +265,8 @@ - (FieldValue)parsedQueryValue:(id)input allowArrays:(bool)allowArrays { * @return The parsed value, or nil if the value was a FieldValue sentinel that should not be * included in the resulting parsed data. */ -- (absl::optional)parseData:(id)input context:(ParseContext &&)context { +- (absl::optional>)parseData:(id)input + context:(ParseContext &&)context { input = self.preConverter(input); if ([input isKindOfClass:[NSDictionary class]]) { return [self parseDictionary:(NSDictionary *)input context:std::move(context)]; @@ -281,44 +301,63 @@ - (FieldValue)parsedQueryValue:(id)input allowArrays:(bool)allowArrays { } } -- (FieldValue)parseDictionary:(NSDictionary *)dict - context:(ParseContext &&)context { +- (Message)parseDictionary:(NSDictionary *)dict + context:(ParseContext &&)context { + __block Message result; + result->which_value_type = google_firestore_v1_Value_map_value_tag; + result->map_value.fields_count = 0; + result->map_value.fields = nil; + if (dict.count == 0) { const FieldPath *path = context.path(); if (path && !path->empty()) { context.AddToFieldMask(*path); } - return ObjectValue::Empty().AsFieldValue(); } else { - __block ObjectValue result = ObjectValue::Empty(); + // Compute the final size of the fields array, which contains an entry for + // all fields that are not FieldValue sentinels + __block pb_size_t count = 0; + [dict enumerateKeysAndObjectsUsingBlock:^(NSString *, id value, BOOL *) { + if (![value isKindOfClass:[FIRFieldValue class]]) { + ++count; + } + }]; + + result->map_value.fields_count = count; + result->map_value.fields = nanopb::MakeArray(count); + __block pb_size_t index = 0; [dict enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *) { - absl::optional parsedValue = - [self parseData:value context:context.ChildContext(util::MakeString(key))]; + auto parsedValue = [self parseData:value context:context.ChildContext(util::MakeString(key))]; if (parsedValue) { - FieldPath path = FieldPath{util::MakeString(key)}; - result = result.Set(path, *parsedValue); + result->map_value.fields[index].key = nanopb::MakeBytesArray(util::MakeString(key)); + result->map_value.fields[index].value = *parsedValue->release(); + ++index; } }]; - - return result; } + + return std::move(result); } -- (FieldValue)parseArray:(NSArray *)array context:(ParseContext &&)context { - __block FieldValue::Array result; - result.reserve(array.count); +- (Message)parseArray:(NSArray *)array + context:(ParseContext &&)context { + __block Message result; + result->which_value_type = google_firestore_v1_Value_array_value_tag; + result->array_value.values_count = CheckedSize([array count]); + result->array_value.values = + nanopb::MakeArray(result->array_value.values_count); [array enumerateObjectsUsingBlock:^(id entry, NSUInteger idx, BOOL *) { - absl::optional parsedEntry = [self parseData:entry - context:context.ChildContext(idx)]; + auto parsedEntry = [self parseData:entry context:context.ChildContext(idx)]; if (!parsedEntry) { // Just include nulls in the array for fields being replaced with a sentinel. - parsedEntry = FieldValue::Null(); + parsedEntry = NullValue(); } - result.push_back(*parsedEntry); + result->array_value.values[idx] = *parsedEntry->release(); }]; - return FieldValue::FromArray(std::move(result)); + + return std::move(result); } /** @@ -358,21 +397,20 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex context.AddToFieldTransforms(*context.path(), ServerTimestampTransform()); } else if ([fieldValue isKindOfClass:[FSTArrayUnionFieldValue class]]) { - std::vector parsedElements = + auto parsedElements = [self parseArrayTransformElements:((FSTArrayUnionFieldValue *)fieldValue).elements]; - ArrayTransform array_union(TransformOperation::Type::ArrayUnion, std::move(parsedElements)); - context.AddToFieldTransforms(*context.path(), std::move(array_union)); + ArrayTransform arrayUnion(TransformOperation::Type::ArrayUnion, std::move(parsedElements)); + context.AddToFieldTransforms(*context.path(), std::move(arrayUnion)); } else if ([fieldValue isKindOfClass:[FSTArrayRemoveFieldValue class]]) { - std::vector parsedElements = + auto parsedElements = [self parseArrayTransformElements:((FSTArrayRemoveFieldValue *)fieldValue).elements]; - ArrayTransform array_remove(TransformOperation::Type::ArrayRemove, std::move(parsedElements)); - context.AddToFieldTransforms(*context.path(), std::move(array_remove)); + ArrayTransform arrayRemove(TransformOperation::Type::ArrayRemove, std::move(parsedElements)); + context.AddToFieldTransforms(*context.path(), std::move(arrayRemove)); } else if ([fieldValue isKindOfClass:[FSTNumericIncrementFieldValue class]]) { - FSTNumericIncrementFieldValue *numericIncrementFieldValue = - (FSTNumericIncrementFieldValue *)fieldValue; - FieldValue operand = [self parsedQueryValue:numericIncrementFieldValue.operand]; + auto *numericIncrementFieldValue = (FSTNumericIncrementFieldValue *)fieldValue; + auto operand = [self parsedQueryValue:numericIncrementFieldValue.operand]; NumericIncrementTransform numeric_increment(std::move(operand)); context.AddToFieldTransforms(*context.path(), std::move(numeric_increment)); @@ -392,9 +430,10 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex * * @return The parsed value. */ -- (absl::optional)parseScalarValue:(nullable id)input context:(ParseContext &&)context { +- (Message)parseScalarValue:(nullable id)input + context:(ParseContext &&)context { if (!input || [input isMemberOfClass:[NSNull class]]) { - return FieldValue::Null(); + return NullValue(); } else if ([input isKindOfClass:[NSNumber class]]) { // Recover the underlying type of the number, using the method described here: @@ -406,7 +445,7 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex // Articles/ocrtTypeEncodings.html switch (cType[0]) { case 'q': - return FieldValue::FromInteger([input longLongValue]); + return [self encodeInteger:[input longLongValue]]; case 'i': // Falls through. case 's': // Falls through. @@ -415,7 +454,7 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex case 'S': // Coerce integer values that aren't long long. Allow unsigned integer types that are // guaranteed small enough to skip a length check. - return FieldValue::FromInteger([input longLongValue]); + return [self encodeInteger:[input longLongValue]]; case 'L': // Falls through. case 'Q': @@ -429,19 +468,19 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex context.FieldDescription()); } else { - return FieldValue::FromInteger(static_cast(extended)); + return [self encodeInteger:static_cast(extended)]; } } case 'f': - return FieldValue::FromDouble([input doubleValue]); + return [self encodeDouble:[input doubleValue]]; case 'd': // Double values are already the right type, so just reuse the existing boxed double. // // Note that NSNumber already performs NaN normalization to a single shared instance // so there's no need to treat NaN specially here. - return FieldValue::FromDouble([input doubleValue]); + return [self encodeDouble:[input doubleValue]]; case 'B': // Falls through. case 'c': // Falls through. @@ -453,7 +492,7 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex // legitimate usage of signed chars is impossible, but this should be rare. // // Additionally, for consistency, map unsigned chars to bools in the same way. - return FieldValue::FromBoolean([input boolValue]); + return [self encodeBoolean:[input boolValue]]; default: // All documented codes should be handled above, so this shouldn't happen. @@ -461,23 +500,23 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex } } else if ([input isKindOfClass:[NSString class]]) { - return FieldValue::FromString(util::MakeString(input)); + std::string inputString = util::MakeString(input); + return [self encodeStringValue:inputString]; } else if ([input isKindOfClass:[NSDate class]]) { NSDate *inputDate = input; - return FieldValue::FromTimestamp(api::MakeTimestamp(inputDate)); + return [self encodeTimestampValue:api::MakeTimestamp(inputDate)]; } else if ([input isKindOfClass:[FIRTimestamp class]]) { FIRTimestamp *inputTimestamp = input; Timestamp timestamp = TimestampInternal::Truncate(api::MakeTimestamp(inputTimestamp)); - return FieldValue::FromTimestamp(timestamp); + return [self encodeTimestampValue:timestamp]; } else if ([input isKindOfClass:[FIRGeoPoint class]]) { - return FieldValue::FromGeoPoint(api::MakeGeoPoint(input)); - + return [self encodeGeoPoint:api::MakeGeoPoint(input)]; } else if ([input isKindOfClass:[NSData class]]) { NSData *inputData = input; - return FieldValue::FromBlob(MakeByteString(inputData)); + return [self encodeBlob:(nanopb::MakeByteString(inputData))]; } else if ([input isKindOfClass:[FSTDocumentKeyReference class]]) { FSTDocumentKeyReference *reference = input; @@ -488,7 +527,7 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex other.project_id(), other.database_id(), _databaseID.project_id(), _databaseID.database_id(), context.FieldDescription()); } - return FieldValue::FromReference(_databaseID, reference.key); + return [self encodeReference:_databaseID key:reference.key]; } else { ThrowInvalidArgument("Unsupported type: %s%s", NSStringFromClass([input class]), @@ -496,23 +535,92 @@ - (void)parseSentinelFieldValue:(FIRFieldValue *)fieldValue context:(ParseContex } } -- (std::vector)parseArrayTransformElements:(NSArray *)elements { +- (Message)encodeBoolean:(bool)value { + Message result; + result->which_value_type = google_firestore_v1_Value_boolean_value_tag; + result->boolean_value = value; + return result; +} + +- (Message)encodeInteger:(int64_t)value { + Message result; + result->which_value_type = google_firestore_v1_Value_integer_value_tag; + result->integer_value = value; + return result; +} + +- (Message)encodeDouble:(double)value { + Message result; + result->which_value_type = google_firestore_v1_Value_double_value_tag; + result->double_value = value; + return result; +} + +- (Message)encodeTimestampValue:(Timestamp)value { + Message result; + result->which_value_type = google_firestore_v1_Value_timestamp_value_tag; + result->timestamp_value.seconds = value.seconds(); + result->timestamp_value.nanos = value.nanoseconds(); + return result; +} + +- (Message)encodeStringValue:(const std::string &)value { + Message result; + result->which_value_type = google_firestore_v1_Value_string_value_tag; + result->string_value = nanopb::MakeBytesArray(value); + return result; +} + +- (Message)encodeBlob:(const nanopb::ByteString &)value { + Message result; + result->which_value_type = google_firestore_v1_Value_bytes_value_tag; + // Copy the blob so that pb_release can do the right thing. + result->bytes_value = nanopb::CopyBytesArray(value.get()); + return result; +} + +- (Message)encodeReference:(const DatabaseId &)databaseId + key:(const DocumentKey &)key { + HARD_ASSERT(_databaseID == databaseId, "Database %s cannot encode reference from %s", + _databaseID.ToString(), databaseId.ToString()); + + std::string referenceName = ResourcePath({"projects", databaseId.project_id(), "databases", + databaseId.database_id(), "documents", key.ToString()}) + .CanonicalString(); + + Message result; + result->which_value_type = google_firestore_v1_Value_reference_value_tag; + result->reference_value = nanopb::MakeBytesArray(referenceName); + return result; +} + +- (Message)encodeGeoPoint:(const GeoPoint &)value { + Message result; + result->which_value_type = google_firestore_v1_Value_geo_point_value_tag; + result->geo_point_value.latitude = value.latitude(); + result->geo_point_value.longitude = value.longitude(); + return result; +} + +- (Message)parseArrayTransformElements:(NSArray *)elements { ParseAccumulator accumulator{UserDataSource::Argument}; - std::vector values; + Message array_value; + array_value->values_count = CheckedSize(elements.count); + array_value->values = nanopb::MakeArray(array_value->values_count); + for (NSUInteger i = 0; i < elements.count; i++) { id element = elements[i]; // Although array transforms are used with writes, the actual elements being unioned or removed // are not considered writes since they cannot contain any FieldValue sentinels, etc. ParseContext context = accumulator.RootContext(); - absl::optional parsedElement = [self parseData:element - context:context.ChildContext(i)]; + auto parsedElement = [self parseData:element context:context.ChildContext(i)]; HARD_ASSERT(parsedElement && accumulator.field_transforms().empty(), "Failed to properly parse array transform element: %s", element); - values.push_back(*parsedElement); + array_value->values[i] = *parsedElement->release(); } - return values; + return array_value; } @end diff --git a/Firestore/Source/API/FSTUserDataWriter.h b/Firestore/Source/API/FSTUserDataWriter.h new file mode 100644 index 00000000000..2f0f3d67633 --- /dev/null +++ b/Firestore/Source/API/FSTUserDataWriter.h @@ -0,0 +1,40 @@ +/* + * Copyright 2021 Google LLC + * + * 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 + +#include + +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/Source/API/FIRDocumentSnapshot+Internal.h" +#include "Firestore/core/src/api/api_fwd.h" + +namespace api = firebase::firestore::api; + +/** + * Converts Firestore's internal types to the API types that we expose to the + * user. + */ +@interface FSTUserDataWriter : NSObject + +- (instancetype)init NS_UNAVAILABLE; + +- (instancetype)initWithFirestore:(std::shared_ptr)firestore + serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior; + +- (id)convertedValue:(const firebase::firestore::google_firestore_v1_Value&)value; + +@end diff --git a/Firestore/Source/API/FSTUserDataWriter.mm b/Firestore/Source/API/FSTUserDataWriter.mm new file mode 100644 index 00000000000..72c85998ff7 --- /dev/null +++ b/Firestore/Source/API/FSTUserDataWriter.mm @@ -0,0 +1,166 @@ +// Copyright 2021 Google LLC +// +// 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/Source/API/FSTUserDataWriter.h" + +#import +#import +#import + +#include +#include + +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/Source/API/FIRDocumentReference+Internal.h" +#include "Firestore/Source/API/converters.h" +#include "Firestore/core/include/firebase/firestore/geo_point.h" +#include "Firestore/core/include/firebase/firestore/timestamp.h" +#include "Firestore/core/src/api/firestore.h" +#include "Firestore/core/src/model/database_id.h" +#include "Firestore/core/src/model/document_key.h" +#include "Firestore/core/src/model/server_timestamp_util.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/util/log.h" +#include "Firestore/core/src/util/string_apple.h" + +@class FIRTimestamp; + +namespace api = firebase::firestore::api; +namespace util = firebase::firestore::util; +namespace model = firebase::firestore::model; +namespace nanopb = firebase::firestore::nanopb; + +using api::MakeFIRDocumentReference; +using api::MakeFIRGeoPoint; +using api::MakeFIRTimestamp; +using firebase::firestore::GeoPoint; +using firebase::firestore::google_firestore_v1_ArrayValue; +using firebase::firestore::google_firestore_v1_MapValue; +using firebase::firestore::google_firestore_v1_Value; +using firebase::firestore::google_protobuf_Timestamp; +using model::DatabaseId; +using model::DocumentKey; +using model::GetLocalWriteTime; +using model::GetPreviousValue; +using model::GetTypeOrder; +using model::TypeOrder; +using nanopb::MakeByteString; +using nanopb::MakeBytesArray; +using nanopb::MakeNSData; +using nanopb::MakeString; +using nanopb::MakeStringView; + +NS_ASSUME_NONNULL_BEGIN + +@implementation FSTUserDataWriter { + std::shared_ptr _firestore; + FIRServerTimestampBehavior _serverTimestampBehavior; +} + +- (instancetype)initWithFirestore:(std::shared_ptr)firestore + serverTimestampBehavior:(FIRServerTimestampBehavior)serverTimestampBehavior { + self = [super init]; + if (self) { + _firestore = std::move(firestore); + _serverTimestampBehavior = serverTimestampBehavior; + } + return self; +} + +- (id)convertedValue:(const google_firestore_v1_Value &)value { + switch (GetTypeOrder(value)) { + case TypeOrder::kMap: + return [self convertedObject:value.map_value]; + case TypeOrder::kArray: + return [self convertedArray:value.array_value]; + case TypeOrder::kReference: + return [self convertedReference:value]; + case TypeOrder::kTimestamp: + return [self convertedTimestamp:value.timestamp_value]; + case TypeOrder::kServerTimestamp: + return [self convertedServerTimestamp:value]; + case TypeOrder::kNull: + return [NSNull null]; + case TypeOrder::kBoolean: + return value.boolean_value ? @YES : @NO; + case TypeOrder::kNumber: + return value.which_value_type == google_firestore_v1_Value_integer_value_tag + ? @(value.integer_value) + : @(value.double_value); + case TypeOrder::kString: + return util::MakeNSString(MakeStringView(value.string_value)); + case TypeOrder::kBlob: + return MakeNSData(value.bytes_value); + case TypeOrder::kGeoPoint: + return MakeFIRGeoPoint( + GeoPoint(value.geo_point_value.latitude, value.geo_point_value.longitude)); + } + + UNREACHABLE(); +} + +- (NSDictionary *)convertedObject:(const google_firestore_v1_MapValue &)mapValue { + NSMutableDictionary *result = [NSMutableDictionary dictionary]; + for (pb_size_t i = 0; i < mapValue.fields_count; ++i) { + absl::string_view key = MakeStringView(mapValue.fields[i].key); + const google_firestore_v1_Value &value = mapValue.fields[i].value; + result[util::MakeNSString(key)] = [self convertedValue:value]; + } + return result; +} + +- (NSArray *)convertedArray:(const google_firestore_v1_ArrayValue &)arrayValue { + NSMutableArray *result = [NSMutableArray arrayWithCapacity:arrayValue.values_count]; + for (pb_size_t i = 0; i < arrayValue.values_count; ++i) { + [result addObject:[self convertedValue:arrayValue.values[i]]]; + } + return result; +} + +- (id)convertedServerTimestamp:(const google_firestore_v1_Value &)serverTimestampValue { + switch (_serverTimestampBehavior) { + case FIRServerTimestampBehavior::FIRServerTimestampBehaviorNone: + return [NSNull null]; + case FIRServerTimestampBehavior::FIRServerTimestampBehaviorEstimate: + return [self convertedTimestamp:GetLocalWriteTime(serverTimestampValue)]; + case FIRServerTimestampBehavior::FIRServerTimestampBehaviorPrevious: { + auto previous_value = GetPreviousValue(serverTimestampValue); + return previous_value ? [self convertedValue:*previous_value] : [NSNull null]; + } + } + + UNREACHABLE(); +} + +- (FIRTimestamp *)convertedTimestamp:(const google_protobuf_Timestamp &)value { + return MakeFIRTimestamp(firebase::Timestamp{value.seconds, value.nanos}); +} + +- (FIRDocumentReference *)convertedReference:(const google_firestore_v1_Value &)value { + std::string ref = MakeString(value.reference_value); + DatabaseId databaseID = DatabaseId::FromName(ref); + DocumentKey key = DocumentKey::FromName(ref); + if (databaseID != _firestore->database_id()) { + LOG_WARN("Document reference is for a different database (%s/%s) which " + "is not supported. It will be treated as a reference within the current database " + "(%s/%s) instead.", + databaseID.project_id(), databaseID.database_id(), databaseID.project_id(), + databaseID.database_id()); + } + return MakeFIRDocumentReference(key, _firestore); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/Firestore/Source/API/converters.h b/Firestore/Source/API/converters.h index 199df59cdc0..f5d0e4545b8 100644 --- a/Firestore/Source/API/converters.h +++ b/Firestore/Source/API/converters.h @@ -23,8 +23,11 @@ #import +#include + @class FIRGeoPoint; @class FIRTimestamp; +@class FIRDocumentReference; NS_ASSUME_NONNULL_BEGIN @@ -36,8 +39,14 @@ namespace firestore { class GeoPoint; +namespace model { +class DocumentKey; +} + namespace api { +class Firestore; + /** Converts a user-supplied FIRGeoPoint to the equivalent C++ GeoPoint. */ GeoPoint MakeGeoPoint(FIRGeoPoint* geo_point); @@ -50,6 +59,9 @@ Timestamp MakeTimestamp(NSDate* date); FIRTimestamp* MakeFIRTimestamp(const Timestamp& timestamp); +FIRDocumentReference* MakeFIRDocumentReference(const model::DocumentKey& document_key, + std::shared_ptr firestore); + } // namespace api } // namespace firestore } // namespace firebase diff --git a/Firestore/Source/API/converters.mm b/Firestore/Source/API/converters.mm index bc96fb153a8..251d3e578bf 100644 --- a/Firestore/Source/API/converters.mm +++ b/Firestore/Source/API/converters.mm @@ -16,11 +16,16 @@ #include "Firestore/Source/API/converters.h" +#include + #import "FIRGeoPoint.h" #import "FIRTimestamp.h" +#include "Firestore/Source/API/FIRDocumentReference+Internal.h" #include "Firestore/core/include/firebase/firestore/geo_point.h" #include "Firestore/core/include/firebase/firestore/timestamp.h" +#include "Firestore/core/src/api/firestore.h" +#include "Firestore/core/src/model/document_key.h" NS_ASSUME_NONNULL_BEGIN @@ -51,6 +56,11 @@ Timestamp MakeTimestamp(NSDate* date) { nanoseconds:timestamp.nanoseconds()]; } +FIRDocumentReference* MakeFIRDocumentReference(const model::DocumentKey& key, + std::shared_ptr firestore) { + return [[FIRDocumentReference alloc] initWithKey:key firestore:std::move(firestore)]; +} + } // namespace api } // namespace firestore } // namespace firebase diff --git a/Firestore/core/CMakeLists.txt b/Firestore/core/CMakeLists.txt index ec4806bc392..833a6929610 100644 --- a/Firestore/core/CMakeLists.txt +++ b/Firestore/core/CMakeLists.txt @@ -131,11 +131,12 @@ target_compile_definitions( target_link_libraries( firestore_util PUBLIC - absl_base - absl_memory - absl_meta - absl_optional - absl_strings + absl::base + absl::flat_hash_map + absl::memory + absl::meta + absl::optional + absl::strings absl::time ) @@ -235,11 +236,12 @@ target_include_directories( target_link_libraries( firestore_core PUBLIC LevelDB::LevelDB - absl_base - absl_memory - absl_meta - absl_optional - absl_strings + absl::base + absl::flat_hash_map + absl::memory + absl::meta + absl::optional + absl::strings firestore_nanopb firestore_protos_nanopb firestore_util diff --git a/Firestore/core/src/api/document_snapshot.cc b/Firestore/core/src/api/document_snapshot.cc index 0f4e4825cda..c16573d7ea3 100644 --- a/Firestore/core/src/api/document_snapshot.cc +++ b/Firestore/core/src/api/document_snapshot.cc @@ -16,8 +16,9 @@ #include "Firestore/core/src/api/document_snapshot.h" +#include + #include "Firestore/core/src/api/document_reference.h" -#include "Firestore/core/src/model/field_value.h" #include "Firestore/core/src/model/resource_path.h" #include "Firestore/core/src/util/hashing.h" #include "absl/types/optional.h" @@ -29,14 +30,13 @@ namespace api { using model::Document; using model::DocumentKey; using model::FieldPath; -using model::FieldValue; using model::ObjectValue; DocumentSnapshot DocumentSnapshot::FromDocument( std::shared_ptr firestore, model::Document document, SnapshotMetadata metadata) { - return DocumentSnapshot{std::move(firestore), document.key(), document, + return DocumentSnapshot{std::move(firestore), document->key(), document, std::move(metadata)}; } @@ -44,7 +44,7 @@ DocumentSnapshot DocumentSnapshot::FromNoDocument( std::shared_ptr firestore, model::DocumentKey key, SnapshotMetadata metadata) { - return DocumentSnapshot{std::move(firestore), key, absl::nullopt, + return DocumentSnapshot{std::move(firestore), std::move(key), absl::nullopt, std::move(metadata)}; } @@ -79,15 +79,10 @@ const std::string& DocumentSnapshot::document_id() const { return internal_key_.path().last_segment(); } -absl::optional DocumentSnapshot::GetData() const { - return internal_document_ ? internal_document_->data() - : absl::optional{}; -} - -absl::optional DocumentSnapshot::GetValue( +absl::optional DocumentSnapshot::GetValue( const FieldPath& field_path) const { - return internal_document_ ? internal_document_->field(field_path) - : absl::optional{}; + return internal_document_ ? (*internal_document_)->field(field_path) + : absl::nullopt; } bool operator==(const DocumentSnapshot& lhs, const DocumentSnapshot& rhs) { diff --git a/Firestore/core/src/api/document_snapshot.h b/Firestore/core/src/api/document_snapshot.h index a7da6794e85..f1e4ef29f05 100644 --- a/Firestore/core/src/api/document_snapshot.h +++ b/Firestore/core/src/api/document_snapshot.h @@ -59,8 +59,7 @@ class DocumentSnapshot { DocumentReference CreateReference() const; - absl::optional GetData() const; - absl::optional GetValue( + absl::optional GetValue( const model::FieldPath& field_path) const; const std::shared_ptr& firestore() const { diff --git a/Firestore/core/src/api/query_core.cc b/Firestore/core/src/api/query_core.cc index 8c00af2aa29..73232846f1f 100644 --- a/Firestore/core/src/api/query_core.cc +++ b/Firestore/core/src/api/query_core.cc @@ -31,11 +31,13 @@ #include "Firestore/core/src/core/firestore_client.h" #include "Firestore/core/src/core/listen_options.h" #include "Firestore/core/src/core/operator.h" -#include "Firestore/core/src/model/field_value.h" #include "Firestore/core/src/model/resource_path.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" #include "Firestore/core/src/util/exception.h" #include "absl/algorithm/container.h" #include "absl/strings/match.h" +#include "absl/types/span.h" namespace firebase { namespace firestore { @@ -55,8 +57,13 @@ using core::QueryListener; using core::ViewSnapshot; using model::DocumentKey; using model::FieldPath; -using model::FieldValue; +using model::GetTypeOrder; +using model::IsArray; +using model::RefValue; using model::ResourcePath; +using model::TypeOrder; +using nanopb::MakeSharedMessage; +using nanopb::Message; using util::Status; using util::StatusOr; using util::ThrowInvalidArgument; @@ -223,9 +230,9 @@ std::unique_ptr Query::AddSnapshotListener( std::move(query_listener)); } -Query Query::Filter(FieldPath field_path, +Query Query::Filter(const FieldPath& field_path, Operator op, - FieldValue field_value, + nanopb::SharedMessage value, const std::function& type_describer) const { if (field_path.IsKeyFieldPath()) { if (IsArrayOperator(op)) { @@ -234,30 +241,38 @@ Query Query::Filter(FieldPath field_path, "ID since document IDs are not arrays.", Describe(op)); } else if (op == Operator::In || op == Operator::NotIn) { - ValidateDisjunctiveFilterElements(field_value, op); - std::vector references; - for (const auto& array_value : field_value.array_value()) { - references.push_back( - ParseExpectedReferenceValue(array_value, type_describer)); - } - field_value = FieldValue::FromArray(references); + ValidateDisjunctiveFilterElements(*value, op); + // TODO(mutabledocuments): See if we can remove this copy and modify the + // input values directly. + auto references = MakeSharedMessage({}); + references->which_value_type = google_firestore_v1_Value_array_value_tag; + nanopb::SetRepeatedField( + &references->array_value.values, + &references->array_value.values_count, + absl::Span( + value->array_value.values, value->array_value.values_count), + [&](const google_firestore_v1_Value& value) { + return *ParseExpectedReferenceValue(value, type_describer) + .release(); + }); + value = std::move(references); } else { - field_value = ParseExpectedReferenceValue(field_value, type_describer); + value = ParseExpectedReferenceValue(*value, type_describer); } } else { if (IsDisjunctiveOperator(op)) { - ValidateDisjunctiveFilterElements(field_value, op); + ValidateDisjunctiveFilterElements(*value, op); } } - FieldFilter filter = FieldFilter::Create(field_path, op, field_value); + FieldFilter filter = FieldFilter::Create(field_path, op, std::move(value)); ValidateNewFilter(filter); return Wrap(query_.AddingFilter(std::move(filter))); } Query Query::OrderBy(FieldPath field_path, bool descending) const { - return OrderBy(field_path, Direction::FromDescending(descending)); + return OrderBy(std::move(field_path), Direction::FromDescending(descending)); } Query Query::OrderBy(FieldPath field_path, Direction direction) const { @@ -379,17 +394,17 @@ void Query::ValidateHasExplicitOrderByForLimitToLast() const { } void Query::ValidateDisjunctiveFilterElements( - const model::FieldValue& field_value, Operator op) const { + const google_firestore_v1_Value& value, Operator op) const { HARD_ASSERT( - field_value.type() == FieldValue::Type::Array, + IsArray(value), "A FieldValue of Array type is required for disjunctive filters."); - if (field_value.array_value().empty()) { + if (value.array_value.values_count == 0) { ThrowInvalidArgument( "Invalid Query. A non-empty array is required for '%s'" " filters.", Describe(op)); } - if (field_value.array_value().size() > 10) { + if (value.array_value.values_count > 10) { ThrowInvalidArgument( "Invalid Query. '%s' filters support a maximum of 10" " elements in the value array.", @@ -397,11 +412,11 @@ void Query::ValidateDisjunctiveFilterElements( } } -FieldValue Query::ParseExpectedReferenceValue( - const model::FieldValue& field_value, +Message Query::ParseExpectedReferenceValue( + const google_firestore_v1_Value& value, const std::function& type_describer) const { - if (field_value.type() == FieldValue::Type::String) { - const std::string& document_key = field_value.string_value(); + if (GetTypeOrder(value) == TypeOrder::kString) { + std::string document_key = nanopb::MakeString(value.string_value); if (document_key.empty()) { ThrowInvalidArgument( "Invalid query. When querying by document ID you must provide a " @@ -423,10 +438,9 @@ FieldValue Query::ParseExpectedReferenceValue( "is not because it has an odd number of segments.", path.CanonicalString()); } - return FieldValue::FromReference(firestore_->database_id(), - DocumentKey{path}); - } else if (field_value.type() == FieldValue::Type::Reference) { - return field_value; + return RefValue(firestore_->database_id(), DocumentKey{path}); + } else if (GetTypeOrder(value) == TypeOrder::kReference) { + return model::DeepClone(value); } else { ThrowInvalidArgument( "Invalid query. When querying by document ID you must provide a " diff --git a/Firestore/core/src/api/query_core.h b/Firestore/core/src/api/query_core.h index 1678aca3210..54f7d162790 100644 --- a/Firestore/core/src/api/query_core.h +++ b/Firestore/core/src/api/query_core.h @@ -25,6 +25,7 @@ #include "Firestore/core/src/core/core_fwd.h" #include "Firestore/core/src/core/filter.h" #include "Firestore/core/src/core/query.h" +#include "Firestore/core/src/nanopb/message.h" namespace firebase { namespace firestore { @@ -85,15 +86,15 @@ class Query { * * @param field_path The name of the field to compare. * @param op The operator to apply. - * @param field_value The value against which to compare the field. + * @param value The value against which to compare the field. * @param type_describer A function that will produce a description of the * type of field_value. * * @return The created `Query`. */ - Query Filter(model::FieldPath field_path, + Query Filter(const model::FieldPath& field_path, core::Filter::Operator op, - model::FieldValue field_value, + nanopb::SharedMessage value, const std::function& type_describer) const; /** @@ -180,7 +181,7 @@ class Query { * Validates that the value passed into a disjunctive filter satisfies all * array requirements. */ - void ValidateDisjunctiveFilterElements(const model::FieldValue& field_value, + void ValidateDisjunctiveFilterElements(const google_firestore_v1_Value& value, core::Filter::Operator op) const; /** @@ -188,8 +189,8 @@ class Query { * if the value is anything other than a Reference or String, or if the string * is malformed. */ - model::FieldValue ParseExpectedReferenceValue( - const model::FieldValue& field_value, + nanopb::Message ParseExpectedReferenceValue( + const google_firestore_v1_Value& value, const std::function& type_describer) const; std::string Describe(core::Filter::Operator op) const; diff --git a/Firestore/core/src/api/query_snapshot.cc b/Firestore/core/src/api/query_snapshot.cc index 5ee4103351a..e24d0fc4b1b 100644 --- a/Firestore/core/src/api/query_snapshot.cc +++ b/Firestore/core/src/api/query_snapshot.cc @@ -73,7 +73,8 @@ void QuerySnapshot::ForEachDocument( bool from_cache = metadata_.from_cache(); for (const Document& document : document_set) { - bool has_pending_writes = snapshot_.mutated_keys().contains(document.key()); + bool has_pending_writes = + snapshot_.mutated_keys().contains(document->key()); auto snap = DocumentSnapshot::FromDocument( firestore_, document, SnapshotMetadata(has_pending_writes, from_cache)); callback(std::move(snap)); @@ -115,7 +116,7 @@ void QuerySnapshot::ForEachChange( for (const DocumentViewChange& change : snapshot_.document_changes()) { const Document& doc = change.document(); SnapshotMetadata metadata( - /*pending_writes=*/snapshot_.mutated_keys().contains(doc.key()), + /*pending_writes=*/snapshot_.mutated_keys().contains(doc->key()), /*from_cache=*/snapshot_.from_cache()); auto document = DocumentSnapshot::FromDocument(firestore_, doc, std::move(metadata)); @@ -143,21 +144,21 @@ void QuerySnapshot::ForEachChange( const Document& doc = change.document(); SnapshotMetadata metadata( - /*pending_writes=*/snapshot_.mutated_keys().contains(doc.key()), + /*pending_writes=*/snapshot_.mutated_keys().contains(doc->key()), /*from_cache=*/snapshot_.from_cache()); auto document = DocumentSnapshot::FromDocument(firestore_, doc, metadata); size_t old_index = DocumentChange::npos; size_t new_index = DocumentChange::npos; if (change.type() != DocumentViewChange::Type::Added) { - old_index = index_tracker.IndexOf(change.document().key()); + old_index = index_tracker.IndexOf(change.document()->key()); HARD_ASSERT(old_index != DocumentSet::npos, "Index for document not found"); - index_tracker = index_tracker.erase(change.document().key()); + index_tracker = index_tracker.erase(change.document()->key()); } if (change.type() != DocumentViewChange::Type::Removed) { index_tracker = index_tracker.insert(change.document()); - new_index = index_tracker.IndexOf(change.document().key()); + new_index = index_tracker.IndexOf(change.document()->key()); } DocumentChange::Type type = DocumentChangeTypeForChange(change); diff --git a/Firestore/core/src/bundle/bundle_callback.h b/Firestore/core/src/bundle/bundle_callback.h index a3047ef997c..2ce01de20d8 100644 --- a/Firestore/core/src/bundle/bundle_callback.h +++ b/Firestore/core/src/bundle/bundle_callback.h @@ -20,7 +20,6 @@ #include "Firestore/core/src/bundle/bundle_metadata.h" #include "Firestore/core/src/bundle/named_query.h" -#include "Firestore/core/src/model/document_map.h" namespace firebase { namespace firestore { @@ -41,8 +40,8 @@ class BundleCallback { * Local documents are re-calculated if there are remaining mutations in the * queue. */ - virtual model::MaybeDocumentMap ApplyBundledDocuments( - const model::MaybeDocumentMap& documents, + virtual model::DocumentMap ApplyBundledDocuments( + const model::MutableDocumentMap& documents, const std::string& bundle_id) = 0; /** Saves the given NamedQuery to local persistence. */ diff --git a/Firestore/core/src/bundle/bundle_document.h b/Firestore/core/src/bundle/bundle_document.h index 25bfdfbf512..338b3e61832 100644 --- a/Firestore/core/src/bundle/bundle_document.h +++ b/Firestore/core/src/bundle/bundle_document.h @@ -19,7 +19,7 @@ #include #include "Firestore/core/src/bundle/bundle_element.h" -#include "Firestore/core/src/model/document.h" +#include "Firestore/core/src/model/mutable_document.h" namespace firebase { namespace firestore { @@ -30,7 +30,7 @@ class BundleDocument : public BundleElement { public: BundleDocument() = default; - explicit BundleDocument(model::Document document) + explicit BundleDocument(model::MutableDocument document) : document_(std::move(document)) { } @@ -44,12 +44,12 @@ class BundleDocument : public BundleElement { } /** Returns the document. */ - const model::Document& document() const { + const model::MutableDocument& document() const { return document_; } private: - model::Document document_; + model::MutableDocument document_; }; inline bool operator==(const BundleDocument& lhs, const BundleDocument& rhs) { diff --git a/Firestore/core/src/bundle/bundle_loader.cc b/Firestore/core/src/bundle/bundle_loader.cc index 19fd75c60b2..5f56ddcf2cd 100644 --- a/Firestore/core/src/bundle/bundle_loader.cc +++ b/Firestore/core/src/bundle/bundle_loader.cc @@ -22,10 +22,11 @@ #include "Firestore/core/include/firebase/firestore/firestore_errors.h" #include "Firestore/core/src/api/load_bundle_task.h" #include "Firestore/core/src/bundle/bundle_document.h" +#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/document_key_set.h" #include "Firestore/core/src/model/model_fwd.h" -#include "Firestore/core/src/model/no_document.h" +#include "Firestore/core/src/model/mutable_document.h" namespace firebase { namespace firestore { @@ -35,8 +36,8 @@ using firestore::Error; using firestore::api::LoadBundleTaskProgress; using firestore::api::LoadBundleTaskState; using model::DocumentKeySet; -using model::MaybeDocumentMap; -using model::NoDocument; +using model::DocumentMap; +using model::MutableDocument; using util::Status; using util::StatusOr; @@ -59,8 +60,8 @@ Status BundleLoader::AddElementInternal(const BundleElement& element) { if (!document_metadata.exists()) { documents_ = documents_.insert( document_metadata.key(), - NoDocument(document_metadata.key(), document_metadata.read_time(), - /*has_committed_mutations=*/false)); + MutableDocument::NoDocument(document_metadata.key(), + document_metadata.read_time())); current_document_ = absl::nullopt; } break; @@ -114,15 +115,15 @@ StatusOr> BundleLoader::AddElement( return {absl::make_optional(std::move(progress))}; } -StatusOr BundleLoader::ApplyChanges() { +StatusOr BundleLoader::ApplyChanges() { if (current_document_ != absl::nullopt) { - return StatusOr( + return StatusOr( Status(Error::kErrorInvalidArgument, "Bundled documents end with a document metadata " "element instead of a document.")); } if (metadata_.total_documents() != documents_.size()) { - return StatusOr( + return StatusOr( Status(Error::kErrorInvalidArgument, "Loaded documents count is not the same as in metadata.")); } diff --git a/Firestore/core/src/bundle/bundle_loader.h b/Firestore/core/src/bundle/bundle_loader.h index c8595c2bf92..9a0bb02a254 100644 --- a/Firestore/core/src/bundle/bundle_loader.h +++ b/Firestore/core/src/bundle/bundle_loader.h @@ -27,8 +27,9 @@ #include "Firestore/core/src/bundle/bundle_callback.h" #include "Firestore/core/src/bundle/bundle_element.h" #include "Firestore/core/src/bundle/bundled_document_metadata.h" +#include "Firestore/core/src/immutable/sorted_map.h" #include "Firestore/core/src/model/document_key.h" -#include "Firestore/core/src/model/document_map.h" +#include "Firestore/core/src/model/model_fwd.h" #include "Firestore/core/src/util/statusor.h" #include "absl/types/optional.h" @@ -72,7 +73,7 @@ class BundleLoader { * Applies the loaded documents and queries to local store. Returns the * document view changes. If an error occurred, returns a not `ok()` status. */ - util::StatusOr ApplyChanges(); + util::StatusOr ApplyChanges(); private: /** @@ -95,7 +96,7 @@ class BundleLoader { BundledDocumentMetadata, model::DocumentKeyHash> documents_metadata_; - model::MaybeDocumentMap documents_; + model::MutableDocumentMap documents_; uint64_t bytes_loaded_ = 0; absl::optional current_document_; diff --git a/Firestore/core/src/bundle/bundle_serializer.cc b/Firestore/core/src/bundle/bundle_serializer.cc index 031e4428572..6a64f216dd0 100644 --- a/Firestore/core/src/bundle/bundle_serializer.cc +++ b/Firestore/core/src/bundle/bundle_serializer.cc @@ -26,13 +26,16 @@ #include "Firestore/core/src/core/order_by.h" #include "Firestore/core/src/core/query.h" #include "Firestore/core/src/core/target.h" -#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/resource_path.h" +#include "Firestore/core/src/model/value_util.h" #include "Firestore/core/src/nanopb/byte_string.h" +#include "Firestore/core/src/nanopb/message.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" #include "Firestore/core/src/timestamp_internal.h" #include "Firestore/core/src/util/statusor.h" +#include "Firestore/core/src/util/string_format.h" #include "Firestore/core/src/util/string_util.h" #include "absl/strings/escaping.h" #include "absl/strings/numbers.h" @@ -56,13 +59,20 @@ using core::Target; using model::Document; using model::DocumentKey; using model::FieldPath; -using model::FieldValue; +using model::MutableDocument; +using model::NaNValue; +using model::NullValue; using model::ObjectValue; using model::ResourcePath; using model::SnapshotVersion; using nanopb::ByteString; +using nanopb::MakeSharedMessage; +using nanopb::Message; +using nanopb::SetRepeatedField; +using nanopb::SharedMessage; using nlohmann::json; using util::StatusOr; +using util::StringFormat; template const std::vector& EmptyVector() { @@ -201,7 +211,8 @@ Filter::Operator DecodeFieldFilterOperator(JsonReader& reader, Filter InvalidFilter() { // The exact value doesn't matter. Note that there's no way to create the base // class `Filter`, so it has to be one of the derived classes. - return FieldFilter::Create({}, {}, {}); + return FieldFilter::Create({}, {}, + MakeSharedMessage(google_firestore_v1_Value{})); } Filter DecodeUnaryFilter(JsonReader& reader, const json& filter) { @@ -216,17 +227,13 @@ Filter DecodeUnaryFilter(JsonReader& reader, const json& filter) { } if (op == "IS_NAN") { - return FieldFilter::Create(std::move(path), Filter::Operator::Equal, - FieldValue::Nan()); + return FieldFilter::Create(path, Filter::Operator::Equal, NaNValue()); } else if (op == "IS_NULL") { - return FieldFilter::Create(std::move(path), Filter::Operator::Equal, - FieldValue::Null()); + return FieldFilter::Create(path, Filter::Operator::Equal, NullValue()); } else if (op == "IS_NOT_NAN") { - return FieldFilter::Create(std::move(path), Filter::Operator::NotEqual, - FieldValue::Nan()); + return FieldFilter::Create(path, Filter::Operator::NotEqual, NaNValue()); } else if (op == "IS_NOT_NULL") { - return FieldFilter::Create(std::move(path), Filter::Operator::NotEqual, - FieldValue::Null()); + return FieldFilter::Create(path, Filter::Operator::NotEqual, NullValue()); } reader.Fail("Unexpected unary filter operator: " + op); @@ -290,21 +297,22 @@ LimitType DecodeLimitType(JsonReader& reader, const json& query) { } } -FieldValue DecodeGeoPointValue(JsonReader& reader, const json& geo_json) { - double latitude = reader.OptionalDouble("latitude", geo_json, 0.0); - double longitude = reader.OptionalDouble("longitude", geo_json, 0.0); - - return FieldValue::FromGeoPoint(GeoPoint(latitude, longitude)); +google_type_LatLng DecodeGeoPointValue(JsonReader& reader, + const json& geo_json) { + google_type_LatLng result{}; + result.latitude = reader.OptionalDouble("latitude", geo_json, 0.0); + result.longitude = reader.OptionalDouble("longitude", geo_json, 0.0); + return result; } -FieldValue DecodeBytesValue(JsonReader& reader, - const std::string& bytes_string) { +pb_bytes_array_t* DecodeBytesValue(JsonReader& reader, + const std::string& bytes_string) { std::string decoded; if (!absl::Base64Unescape(bytes_string, &decoded)) { reader.Fail("Failed to decode bytesValue string into binary form"); return {}; } - return FieldValue::FromBlob(ByteString((decoded))); + return nanopb::MakeBytesArray(decoded); } } // namespace @@ -517,15 +525,15 @@ BundledQuery BundleSerializer::DecodeBundledQuery( auto order_bys = DecodeOrderBy(reader, structured_query); auto start_at_bound = DecodeBound(reader, structured_query, "startAt"); - std::shared_ptr start_at; - if (!start_at_bound.position().empty()) { - start_at = std::make_shared(std::move(start_at_bound)); + absl::optional start_at; + if (start_at_bound.position()->values_count > 0) { + start_at = std::move(start_at_bound); } auto end_at_bound = DecodeBound(reader, structured_query, "endAt"); - std::shared_ptr end_at; - if (!end_at_bound.position().empty()) { - end_at = std::make_shared(std::move(end_at_bound)); + absl::optional end_at; + if (end_at_bound.position()->values_count > 0) { + end_at = std::move(end_at_bound); } int32_t limit = DecodeLimit(reader, structured_query); @@ -587,7 +595,7 @@ Filter BundleSerializer::DecodeFieldFilter(JsonReader& reader, const auto& op_string = reader.RequiredString("op", filter); auto op = DecodeFieldFilterOperator(reader, op_string); - FieldValue value = + Message value = DecodeValue(reader, reader.RequiredObject("value", filter)); // Return early if !ok(), because `FieldFilter::Create` will abort with @@ -596,7 +604,7 @@ Filter BundleSerializer::DecodeFieldFilter(JsonReader& reader, return InvalidFilter(); } - return FieldFilter::Create(path, op, value); + return FieldFilter::Create(path, op, std::move(value)); } FilterList BundleSerializer::DecodeCompositeFilter(JsonReader& reader, @@ -622,69 +630,85 @@ FilterList BundleSerializer::DecodeCompositeFilter(JsonReader& reader, Bound BundleSerializer::DecodeBound(JsonReader& reader, const json& query, const char* bound_name) const { - Bound default_bound = Bound({}, false); + Bound default_bound = Bound::FromValue( + MakeSharedMessage({}), false); if (!query.contains(bound_name)) { return default_bound; } const json& bound_json = reader.RequiredObject(bound_name, query); + std::vector values = reader.RequiredArray("values", bound_json); bool before = reader.OptionalBool("before", bound_json); - std::vector positions; - - for (const auto& value : reader.RequiredArray("values", bound_json)) { - positions.push_back(DecodeValue(reader, value)); - } - - return Bound(std::move(positions), before); + auto positions = MakeSharedMessage({}); + SetRepeatedField( + &positions->values, &positions->values_count, values, + [&](const json& j) { return *DecodeValue(reader, j).release(); }); + return Bound::FromValue(std::move(positions), before); } -FieldValue BundleSerializer::DecodeValue(JsonReader& reader, - const json& value) const { +Message BundleSerializer::DecodeValue( + JsonReader& reader, const json& value) const { if (!value.is_object()) { reader.Fail("'value' is not encoded as JSON object"); return {}; } + Message result; if (value.contains("nullValue")) { - return FieldValue::Null(); + result->which_value_type = google_firestore_v1_Value_null_value_tag; + result->null_value = {}; } else if (value.contains("booleanValue")) { + result->which_value_type = google_firestore_v1_Value_boolean_value_tag; auto val = value.at("booleanValue"); if (!val.is_boolean()) { reader.Fail("'booleanValue' is not encoded as a valid boolean"); return {}; } - return FieldValue::FromBoolean(val.get()); + result->boolean_value = val.get(); } else if (value.contains("integerValue")) { - return FieldValue::FromInteger( - reader.RequiredInt("integerValue", value)); + result->which_value_type = google_firestore_v1_Value_integer_value_tag; + result->integer_value = reader.RequiredInt("integerValue", value); } else if (value.contains("doubleValue")) { - return FieldValue::FromDouble(reader.RequiredDouble("doubleValue", value)); + result->which_value_type = google_firestore_v1_Value_double_value_tag; + result->double_value = reader.RequiredDouble("doubleValue", value); } else if (value.contains("timestampValue")) { auto val = DecodeTimestamp(reader, value.at("timestampValue")); - return FieldValue::FromTimestamp(val); + result->which_value_type = google_firestore_v1_Value_timestamp_value_tag; + result->timestamp_value.seconds = val.seconds(); + result->timestamp_value.nanos = val.nanoseconds(); } else if (value.contains("stringValue")) { - auto val = reader.RequiredString("stringValue", value); - return FieldValue::FromString(std::move(val)); + result->which_value_type = google_firestore_v1_Value_string_value_tag; + result->string_value = + nanopb::MakeBytesArray(reader.RequiredString("stringValue", value)); } else if (value.contains("bytesValue")) { - return DecodeBytesValue(reader, reader.RequiredString("bytesValue", value)); + result->which_value_type = google_firestore_v1_Value_bytes_value_tag; + result->bytes_value = + DecodeBytesValue(reader, reader.RequiredString("bytesValue", value)); } else if (value.contains("referenceValue")) { - return DecodeReferenceValue(reader, - reader.RequiredString("referenceValue", value)); + result->which_value_type = google_firestore_v1_Value_reference_value_tag; + result->reference_value = DecodeReferenceValue( + reader, reader.RequiredString("referenceValue", value)); } else if (value.contains("geoPointValue")) { - return DecodeGeoPointValue(reader, value.at("geoPointValue")); + result->which_value_type = google_firestore_v1_Value_geo_point_value_tag; + result->geo_point_value = + DecodeGeoPointValue(reader, value.at("geoPointValue")); } else if (value.contains("arrayValue")) { - return DecodeArrayValue(reader, value.at("arrayValue")); + result->which_value_type = google_firestore_v1_Value_array_value_tag; + result->array_value = + *DecodeArrayValue(reader, value.at("arrayValue")).release(); } else if (value.contains("mapValue")) { - return DecodeMapValue(reader, value.at("mapValue")); + result->which_value_type = google_firestore_v1_Value_map_value_tag; + result->map_value = *DecodeMapValue(reader, value.at("mapValue")).release(); } else { reader.Fail("Failed to decode value, no type is recognized"); return {}; } + return result; } -FieldValue BundleSerializer::DecodeMapValue(JsonReader& reader, - const json& map_json) const { +Message BundleSerializer::DecodeMapValue( + JsonReader& reader, const json& map_json) const { if (!map_json.is_object() || !map_json.contains("fields")) { reader.Fail("mapValue is not a valid map"); return {}; @@ -695,38 +719,41 @@ FieldValue BundleSerializer::DecodeMapValue(JsonReader& reader, return {}; } - immutable::SortedMap field_values; - for (auto it = fields.begin(); it != fields.end(); ++it) { - field_values = - field_values.insert(it.key(), DecodeValue(reader, it.value())); + // Fill the map array. Note that we can't use SetRepeatedField here since the + // JSON map doesn't currently work with SetRepeatedField. + Message map_value; + map_value->fields_count = nanopb::CheckedSize(fields.size()); + map_value->fields = + nanopb::MakeArray( + map_value->fields_count); + pb_size_t i = 0; + for (const auto& entry : fields.items()) { + map_value->fields[i] = {nanopb::MakeBytesArray(entry.key()), + *DecodeValue(reader, entry.value()).release()}; + ++i; } - - return FieldValue::FromMap(std::move(field_values)); + return map_value; } -FieldValue BundleSerializer::DecodeArrayValue(JsonReader& reader, - const json& array_json) const { +Message BundleSerializer::DecodeArrayValue( + JsonReader& reader, const json& array_json) const { const auto& values = reader.RequiredArray("values", array_json); - std::vector field_values; - field_values.reserve(values.size()); - for (const json& json_value : values) { - field_values.push_back(DecodeValue(reader, json_value)); - } - if (!reader.ok()) { - return {}; - } - return FieldValue::FromArray(std::move(field_values)); + Message array_value; + SetRepeatedField( + &array_value->values, &array_value->values_count, values, + [&](const json& j) { return *DecodeValue(reader, j).release(); }); + return array_value; } -FieldValue BundleSerializer::DecodeReferenceValue( +pb_bytes_array_t* BundleSerializer::DecodeReferenceValue( JsonReader& reader, const std::string& ref_string) const { - // Check if ref_string is indeed a valid string passed in. - if (!reader.ok()) { - return {}; + if (reader.ok() && !rpc_serializer_.IsLocalDocumentKey(ref_string)) { + reader.Fail( + StringFormat("Tried to deserialize an invalid key: %s", ref_string)); } - return rpc_serializer_.DecodeReference(&reader, ref_string); + return nanopb::MakeBytesArray(ref_string); } BundledDocumentMetadata BundleSerializer::DecodeDocumentMetadata( @@ -775,9 +802,9 @@ BundleDocument BundleSerializer::DecodeDocument(JsonReader& reader, auto map_value = DecodeMapValue(reader, document); - return BundleDocument(Document(ObjectValue::FromMap(map_value.object_value()), - std::move(key), update_time, - model::DocumentState::kSynced)); + return BundleDocument(MutableDocument::FoundDocument( + std::move(key), update_time, + ObjectValue::FromMapValue(std::move(map_value)))); } } // namespace bundle diff --git a/Firestore/core/src/bundle/bundle_serializer.h b/Firestore/core/src/bundle/bundle_serializer.h index 85f6a55b206..d4ae53360b8 100644 --- a/Firestore/core/src/bundle/bundle_serializer.h +++ b/Firestore/core/src/bundle/bundle_serializer.h @@ -28,6 +28,7 @@ #include "Firestore/core/src/core/core_fwd.h" #include "Firestore/core/src/model/resource_path.h" #include "Firestore/core/src/model/snapshot_version.h" +#include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/remote/serializer.h" #include "Firestore/core/src/util/read_context.h" #include "Firestore/third_party/nlohmann_json/json.hpp" @@ -114,21 +115,21 @@ class BundleSerializer { const nlohmann::json& filter) const; core::FilterList DecodeCompositeFilter(JsonReader& reader, const nlohmann::json& filter) const; - model::FieldValue DecodeValue(JsonReader& reader, - const nlohmann::json& value) const; + nanopb::Message DecodeValue( + JsonReader& reader, const nlohmann::json& value) const; core::Bound DecodeBound(JsonReader& reader, const nlohmann::json& query, const char* bound_name) const; model::ResourcePath DecodeName(JsonReader& reader, const nlohmann::json& name) const; + nanopb::Message DecodeArrayValue( + JsonReader& reader, const nlohmann::json& array_json) const; + nanopb::Message DecodeMapValue( + JsonReader& reader, const nlohmann::json& map_json) const; + pb_bytes_array_t* DecodeReferenceValue(JsonReader& reader, + const std::string& ref_string) const; remote::Serializer rpc_serializer_; - model::FieldValue DecodeReferenceValue(JsonReader& reader, - const std::string& ref_string) const; - model::FieldValue DecodeArrayValue(JsonReader& reader, - const nlohmann::json& array_json) const; - model::FieldValue DecodeMapValue(JsonReader& reader, - const nlohmann::json& map_json) const; }; } // namespace bundle diff --git a/Firestore/core/src/core/array_contains_any_filter.cc b/Firestore/core/src/core/array_contains_any_filter.cc index 9e45a1581b2..bb57b7d56e3 100644 --- a/Firestore/core/src/core/array_contains_any_filter.cc +++ b/Firestore/core/src/core/array_contains_any_filter.cc @@ -20,23 +20,29 @@ #include #include "Firestore/core/src/model/document.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/util/hard_assert.h" #include "absl/algorithm/container.h" namespace firebase { namespace firestore { namespace core { +using model::Contains; using model::Document; using model::FieldPath; -using model::FieldValue; +using model::IsArray; +using nanopb::SharedMessage; using Operator = Filter::Operator; class ArrayContainsAnyFilter::Rep : public FieldFilter::Rep { public: - Rep(FieldPath field, FieldValue value) + Rep(FieldPath field, SharedMessage value) : FieldFilter::Rep( std::move(field), Operator::ArrayContainsAny, std::move(value)) { + HARD_ASSERT(IsArray(this->value()), + "ArrayContainsAnyFilter expects an ArrayValue"); } Type type() const override { @@ -46,21 +52,22 @@ class ArrayContainsAnyFilter::Rep : public FieldFilter::Rep { bool Matches(const model::Document& doc) const override; }; -ArrayContainsAnyFilter::ArrayContainsAnyFilter(FieldPath field, - FieldValue value) - : FieldFilter(std::make_shared(std::move(field), std::move(value))) { +ArrayContainsAnyFilter::ArrayContainsAnyFilter( + const model::FieldPath& field, + SharedMessage value) + : FieldFilter(std::make_shared(field, std::move(value))) { } bool ArrayContainsAnyFilter::Rep::Matches(const Document& doc) const { - const FieldValue::Array& array_value = value().array_value(); - absl::optional maybe_lhs = doc.field(field()); + const google_firestore_v1_ArrayValue& array_value = value().array_value; + absl::optional maybe_lhs = doc->field(field()); if (!maybe_lhs) return false; - const FieldValue& lhs = *maybe_lhs; - if (lhs.type() != FieldValue::Type::Array) return false; + const google_firestore_v1_Value& lhs = *maybe_lhs; + if (!IsArray(lhs)) return false; - for (const auto& val : lhs.array_value()) { - if (absl::c_linear_search(array_value, val)) { + for (pb_size_t i = 0; i < lhs.array_value.values_count; ++i) { + if (Contains(array_value, lhs.array_value.values[i])) { return true; } } diff --git a/Firestore/core/src/core/array_contains_any_filter.h b/Firestore/core/src/core/array_contains_any_filter.h index 537ca99d96f..8367523c978 100644 --- a/Firestore/core/src/core/array_contains_any_filter.h +++ b/Firestore/core/src/core/array_contains_any_filter.h @@ -19,7 +19,9 @@ #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/field_filter.h" +#include "Firestore/core/src/nanopb/message.h" namespace firebase { namespace firestore { @@ -36,7 +38,10 @@ namespace core { */ class ArrayContainsAnyFilter : public FieldFilter { public: - ArrayContainsAnyFilter(model::FieldPath field, model::FieldValue value); + /** Creates a new array-contains-any filter. Takes ownership of `value`. */ + ArrayContainsAnyFilter( + const model::FieldPath& field, + nanopb::SharedMessage value); private: class Rep; diff --git a/Firestore/core/src/core/array_contains_filter.cc b/Firestore/core/src/core/array_contains_filter.cc index d2a4a6b6b71..d0609d83554 100644 --- a/Firestore/core/src/core/array_contains_filter.cc +++ b/Firestore/core/src/core/array_contains_filter.cc @@ -20,21 +20,23 @@ #include #include "Firestore/core/src/model/document.h" +#include "Firestore/core/src/model/value_util.h" #include "absl/algorithm/container.h" namespace firebase { namespace firestore { namespace core { +using model::Contains; using model::Document; using model::FieldPath; -using model::FieldValue; - +using model::IsArray; +using nanopb::SharedMessage; using Operator = Filter::Operator; class ArrayContainsFilter::Rep : public FieldFilter::Rep { public: - Rep(FieldPath field, FieldValue value) + Rep(FieldPath field, SharedMessage value) : FieldFilter::Rep( std::move(field), Operator::ArrayContains, std::move(value)) { } @@ -46,20 +48,21 @@ class ArrayContainsFilter::Rep : public FieldFilter::Rep { bool Matches(const model::Document& doc) const override; }; -ArrayContainsFilter::ArrayContainsFilter(FieldPath field, FieldValue value) - : FieldFilter( - std::make_shared(std::move(field), std::move(value))) { +ArrayContainsFilter::ArrayContainsFilter( + const model::FieldPath& field, + SharedMessage value) + : FieldFilter(std::make_shared(field, std::move(value))) { } bool ArrayContainsFilter::Rep::Matches(const Document& doc) const { - absl::optional maybe_lhs = doc.field(field()); + absl::optional maybe_lhs = doc->field(field()); if (!maybe_lhs) return false; - const FieldValue& lhs = *maybe_lhs; - if (lhs.type() != FieldValue::Type::Array) return false; + const google_firestore_v1_Value& lhs = *maybe_lhs; + if (!IsArray(lhs)) return false; - const FieldValue::Array& contents = lhs.array_value(); - return absl::c_linear_search(contents, value()); + const google_firestore_v1_ArrayValue& contents = lhs.array_value; + return Contains(contents, value()); } } // namespace core diff --git a/Firestore/core/src/core/array_contains_filter.h b/Firestore/core/src/core/array_contains_filter.h index 49b3d7574fb..5f50287b097 100644 --- a/Firestore/core/src/core/array_contains_filter.h +++ b/Firestore/core/src/core/array_contains_filter.h @@ -19,7 +19,9 @@ #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/field_filter.h" +#include "Firestore/core/src/nanopb/message.h" namespace firebase { namespace firestore { @@ -36,7 +38,9 @@ namespace core { */ class ArrayContainsFilter : public FieldFilter { public: - ArrayContainsFilter(model::FieldPath field, model::FieldValue value); + /** Creates a new array-contains filter. Takes ownership of `value`. */ + ArrayContainsFilter(const model::FieldPath& field, + nanopb::SharedMessage value); private: class Rep; diff --git a/Firestore/core/src/core/bound.cc b/Firestore/core/src/core/bound.cc index 07829b8c724..d39d86823a2 100644 --- a/Firestore/core/src/core/bound.cc +++ b/Firestore/core/src/core/bound.cc @@ -21,6 +21,9 @@ #include "Firestore/core/src/core/order_by.h" #include "Firestore/core/src/immutable/append_only_list.h" #include "Firestore/core/src/model/document.h" +#include "Firestore/core/src/model/document_key.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" #include "Firestore/core/src/util/hashing.h" #include "Firestore/core/src/util/to_string.h" @@ -28,36 +31,47 @@ namespace firebase { namespace firestore { namespace core { +using model::Compare; +using model::DocumentKey; using model::FieldPath; -using model::FieldValue; +using model::GetTypeOrder; +using model::TypeOrder; +using nanopb::SharedMessage; using util::ComparisonResult; +Bound Bound::FromValue(SharedMessage position, + bool is_before) { + model::SortFields(*position); + return Bound(std::move(position), is_before); +} + bool Bound::SortsBeforeDocument(const OrderByList& order_by, const model::Document& document) const { - HARD_ASSERT(position_.size() <= order_by.size(), + HARD_ASSERT(position_->values_count <= order_by.size(), "Bound has more components than the provided order by."); ComparisonResult result = ComparisonResult::Same; - for (size_t idx = 0; idx < position_.size(); ++idx) { - const FieldValue& field_value = position_[idx]; + for (size_t idx = 0; idx < position_->values_count; ++idx) { + const google_firestore_v1_Value& field_value = position_->values[idx]; const OrderBy& ordering_component = order_by[idx]; ComparisonResult comparison; if (ordering_component.field() == FieldPath::KeyFieldPath()) { HARD_ASSERT( - field_value.type() == FieldValue::Type::Reference, + GetTypeOrder(field_value) == TypeOrder ::kReference, "Bound has a non-key value where the key path is being used %s", field_value.ToString()); - const auto& ref = field_value.reference_value(); - comparison = ref.key().CompareTo(document.key()); + auto key = DocumentKey::FromName( + nanopb::MakeString(field_value.reference_value)); + comparison = key.CompareTo(document->key()); } else { - absl::optional doc_value = - document.field(ordering_component.field()); + absl::optional doc_value = + document->field(ordering_component.field()); HARD_ASSERT( doc_value.has_value(), "Field should exist since document matched the orderBy already."); - comparison = field_value.CompareTo(*doc_value); + comparison = Compare(field_value, *doc_value); } comparison = ordering_component.direction().ApplyTo(comparison); @@ -73,15 +87,16 @@ bool Bound::SortsBeforeDocument(const OrderByList& order_by, std::string Bound::CanonicalId() const { std::string result = before_ ? "b:" : "a:"; - for (const FieldValue& component : position_) { - result.append(component.ToString()); + for (pb_size_t i = 0; i < position_->values_count; ++i) { + result.append(model::CanonicalId(position_->values[i])); } return result; } std::string Bound::ToString() const { return util::StringFormat("Bound(position=%s, before=%s)", - util::ToString(position_), util::ToString(before_)); + model::CanonicalId(*position_), + util::ToString(before_)); } std::ostream& operator<<(std::ostream& os, const Bound& bound) { @@ -89,11 +104,11 @@ std::ostream& operator<<(std::ostream& os, const Bound& bound) { } bool operator==(const Bound& lhs, const Bound& rhs) { - return lhs.position() == rhs.position() && lhs.before() == rhs.before(); + return *lhs.position() == *rhs.position() && lhs.before() == rhs.before(); } size_t Bound::Hash() const { - return util::Hash(position_, before_); + return util::Hash(model::CanonicalId(*position_), before_); } } // namespace core diff --git a/Firestore/core/src/core/bound.h b/Firestore/core/src/core/bound.h index bd9645b12c7..ca5f36a20fb 100644 --- a/Firestore/core/src/core/bound.h +++ b/Firestore/core/src/core/bound.h @@ -18,13 +18,15 @@ #define FIRESTORE_CORE_SRC_CORE_BOUND_H_ #include +#include #include #include -#include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/core_fwd.h" -#include "Firestore/core/src/model/field_value.h" #include "Firestore/core/src/model/model_fwd.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/nanopb/message.h" namespace firebase { namespace firestore { @@ -53,14 +55,14 @@ class Bound { * @param is_before Whether this bound is just before or just after the * position. */ - Bound(std::vector position, bool is_before) - : position_(std::move(position)), before_(is_before) { - } + static Bound FromValue( + nanopb::SharedMessage position, + bool is_before); /** * The index position of this bound represented as an array of field values. */ - const std::vector& position() const { + const nanopb::SharedMessage position() const { return position_; } @@ -83,7 +85,12 @@ class Bound { size_t Hash() const; private: - std::vector position_; + Bound(nanopb::SharedMessage position, + bool is_before) + : position_{std::move(position)}, before_(is_before) { + } + + nanopb::SharedMessage position_; bool before_; }; diff --git a/Firestore/core/src/core/field_filter.cc b/Firestore/core/src/core/field_filter.cc index a6cbb922ce0..8129bf2d0dc 100644 --- a/Firestore/core/src/core/field_filter.cc +++ b/Firestore/core/src/core/field_filter.cc @@ -28,6 +28,7 @@ #include "Firestore/core/src/core/not_in_filter.h" #include "Firestore/core/src/core/operator.h" #include "Firestore/core/src/model/document.h" +#include "Firestore/core/src/model/value_util.h" #include "Firestore/core/src/util/exception.h" #include "Firestore/core/src/util/hashing.h" #include "absl/algorithm/container.h" @@ -38,8 +39,12 @@ namespace firebase { namespace firestore { namespace core { +using model::Compare; using model::FieldPath; -using model::FieldValue; +using model::GetTypeOrder; +using model::IsArray; +using model::TypeOrder; +using nanopb::SharedMessage; using util::ComparisonResult; namespace { @@ -77,40 +82,34 @@ const char* CanonicalName(Filter::Operator op) { } // namespace -FieldFilter FieldFilter::Create(FieldPath path, - Operator op, - FieldValue value_rhs) { +FieldFilter FieldFilter::Create( + const FieldPath& path, + Operator op, + SharedMessage value_rhs) { + google_firestore_v1_Value& value = *value_rhs; + model::SortFields(value); if (path.IsKeyFieldPath()) { if (op == Filter::Operator::In) { - return KeyFieldInFilter(std::move(path), std::move(value_rhs)); + return KeyFieldInFilter(path, std::move(value_rhs)); } else if (op == Filter::Operator::NotIn) { - return KeyFieldNotInFilter(std::move(path), std::move(value_rhs)); + return KeyFieldNotInFilter(path, std::move(value_rhs)); } else { - HARD_ASSERT(value_rhs.type() == FieldValue::Type::Reference, - "Comparing on key, but filter value not a Reference."); HARD_ASSERT(!IsArrayOperator(op), "%s queries don't make sense on document keys.", CanonicalName(op)); - return KeyFieldFilter(std::move(path), op, std::move(value_rhs)); + return KeyFieldFilter(path, op, std::move(value_rhs)); } } else if (op == Operator::ArrayContains) { - return ArrayContainsFilter(std::move(path), std::move(value_rhs)); + return ArrayContainsFilter(path, std::move(value_rhs)); } else if (op == Operator::In) { - HARD_ASSERT(value_rhs.type() == FieldValue::Type::Array, - "IN filter has invalid value: %s", value_rhs.type()); - return InFilter(std::move(path), std::move(value_rhs)); + return InFilter(path, std::move(value_rhs)); } else if (op == Operator::ArrayContainsAny) { - HARD_ASSERT(value_rhs.type() == FieldValue::Type::Array, - "arrayContainsAny filter has invalid value: %s", - value_rhs.type()); - return ArrayContainsAnyFilter(std::move(path), std::move(value_rhs)); + return ArrayContainsAnyFilter(path, std::move(value_rhs)); } else if (op == Operator::NotIn) { - HARD_ASSERT(value_rhs.type() == FieldValue::Type::Array, - "notIn filter has invalid value: %s", value_rhs.type()); - return NotInFilter(std::move(path), std::move(value_rhs)); + return NotInFilter(path, std::move(value_rhs)); } else { - Rep filter(std::move(path), op, std::move(value_rhs)); + Rep filter(path, op, value_rhs); return FieldFilter(std::make_shared(std::move(filter))); } } @@ -123,7 +122,9 @@ FieldFilter::FieldFilter(std::shared_ptr rep) : Filter(std::move(rep)) { } -FieldFilter::Rep::Rep(FieldPath field, Operator op, FieldValue value_rhs) +FieldFilter::Rep::Rep(FieldPath field, + Operator op, + SharedMessage value_rhs) : field_(std::move(field)), op_(op), value_rhs_(std::move(value_rhs)) { } @@ -134,19 +135,19 @@ bool FieldFilter::Rep::IsInequality() const { } bool FieldFilter::Rep::Matches(const model::Document& doc) const { - absl::optional maybe_lhs = doc.field(field_); + absl::optional maybe_lhs = doc->field(field_); if (!maybe_lhs) return false; - const FieldValue& lhs = *maybe_lhs; + const google_firestore_v1_Value& lhs = *maybe_lhs; // Types do not have to match in NotEqual filters. if (op_ == Operator::NotEqual) { - return MatchesComparison(lhs.CompareTo(value_rhs_)); + return MatchesComparison(Compare(lhs, *value_rhs_)); } // Only compare types with matching backend order (such as double and int). - return FieldValue::Comparable(lhs.type(), value_rhs_.type()) && - MatchesComparison(lhs.CompareTo(value_rhs_)); + return GetTypeOrder(lhs) == GetTypeOrder(*value_rhs_) && + MatchesComparison(Compare(lhs, *value_rhs_)); } bool FieldFilter::Rep::MatchesComparison(ComparisonResult comparison) const { @@ -172,16 +173,17 @@ bool FieldFilter::Rep::MatchesComparison(ComparisonResult comparison) const { std::string FieldFilter::Rep::CanonicalId() const { return absl::StrCat(field_.CanonicalString(), CanonicalName(op_), - value_rhs_.ToString()); + model::CanonicalId(*value_rhs_)); } std::string FieldFilter::Rep::ToString() const { return util::StringFormat("%s %s %s", field_.CanonicalString(), - CanonicalName(op_), value_rhs_.ToString()); + CanonicalName(op_), + model::CanonicalId(*value_rhs_)); } size_t FieldFilter::Rep::Hash() const { - return util::Hash(field_, op_, value_rhs_); + return util::Hash(field_, op_, model::CanonicalId(*value_rhs_)); } bool FieldFilter::Rep::Equals(const Filter::Rep& other) const { @@ -189,7 +191,7 @@ bool FieldFilter::Rep::Equals(const Filter::Rep& other) const { const auto& other_rep = static_cast(other); return op_ == other_rep.op_ && field_ == other_rep.field_ && - value_rhs_ == other_rep.value_rhs_; + *value_rhs_ == *other_rep.value_rhs_; } } // namespace core diff --git a/Firestore/core/src/core/field_filter.h b/Firestore/core/src/core/field_filter.h index 174fedc4b02..027f8b6fa02 100644 --- a/Firestore/core/src/core/field_filter.h +++ b/Firestore/core/src/core/field_filter.h @@ -20,9 +20,10 @@ #include #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/filter.h" #include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/nanopb/message.h" namespace firebase { namespace firestore { @@ -42,9 +43,10 @@ class FieldFilter : public Filter { /** * Creates a Filter instance for the provided path, operator, and value. */ - static FieldFilter Create(model::FieldPath path, - Operator op, - model::FieldValue value_rhs); + static FieldFilter Create( + const model::FieldPath& path, + Operator op, + nanopb::SharedMessage value_rhs); explicit FieldFilter(const Filter& other); @@ -56,8 +58,8 @@ class FieldFilter : public Filter { return field_filter_rep().op_; } - const model::FieldValue& value() const { - return field_filter_rep().value_rhs_; + const google_firestore_v1_Value& value() const { + return *(field_filter_rep().value_rhs_); } protected: @@ -81,8 +83,8 @@ class FieldFilter : public Filter { return op_; } - const model::FieldValue& value() const { - return value_rhs_; + const google_firestore_v1_Value& value() const { + return *value_rhs_; } bool Matches(const model::Document& doc) const override; @@ -98,13 +100,17 @@ class FieldFilter : public Filter { * Creates a new filter that compares fields and values. Only intended to be * called from Filter::Create(). * + * The FieldFilter takes ownership of `value_rhs`. + * * @param field A path to a field in the document to filter on. The LHS of * the expression. * @param op The binary operator to apply. * @param value_rhs A constant value to compare `field` to. The RHS of the * expression. */ - Rep(model::FieldPath field, Operator op, model::FieldValue value_rhs); + Rep(model::FieldPath field, + Operator op, + nanopb::SharedMessage value_rhs); bool MatchesComparison(util::ComparisonResult comparison) const; @@ -113,8 +119,6 @@ class FieldFilter : public Filter { bool Equals(const Filter::Rep& other) const override; - bool MatchesValue(const model::FieldValue& lhs) const; - /** The left hand side of the relation. A path into a document field. */ model::FieldPath field_; @@ -122,7 +126,7 @@ class FieldFilter : public Filter { Operator op_; /** The right hand side of the relation. A constant value to compare to. */ - model::FieldValue value_rhs_; + nanopb::SharedMessage value_rhs_; }; explicit FieldFilter(std::shared_ptr rep); diff --git a/Firestore/core/src/core/firestore_client.cc b/Firestore/core/src/core/firestore_client.cc index 774cdd1faab..89f91b56ade 100644 --- a/Firestore/core/src/core/firestore_client.cc +++ b/Firestore/core/src/core/firestore_client.cc @@ -43,6 +43,7 @@ #include "Firestore/core/src/local/query_engine.h" #include "Firestore/core/src/local/query_result.h" #include "Firestore/core/src/model/database_id.h" +#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_set.h" #include "Firestore/core/src/model/mutation.h" #include "Firestore/core/src/remote/connectivity_monitor.h" @@ -82,7 +83,7 @@ using local::QueryEngine; using local::QueryResult; using model::Document; using model::DocumentKeySet; -using model::MaybeDocument; +using model::DocumentMap; using model::Mutation; using model::OnlineState; using remote::ConnectivityMonitor; @@ -388,17 +389,15 @@ void FirestoreClient::GetDocumentFromLocalCache( // TODO(c++14): move `callback` into lambda. auto shared_callback = absl::ShareUniquePtr(std::move(callback)); worker_queue_->Enqueue([this, doc, shared_callback] { - absl::optional maybe_document = - local_store_->ReadDocument(doc.key()); + Document document = local_store_->ReadDocument(doc.key()); StatusOr maybe_snapshot; - if (maybe_document && maybe_document->is_document()) { - Document document(*maybe_document); + if (document->is_found_document()) { maybe_snapshot = DocumentSnapshot::FromDocument( doc.firestore(), document, - SnapshotMetadata{/*pending_writes=*/document.has_local_mutations(), + SnapshotMetadata{document->has_local_mutations(), /*from_cache=*/true}); - } else if (maybe_document && maybe_document->is_no_document()) { + } else if (document->is_no_document()) { maybe_snapshot = DocumentSnapshot::FromNoDocument( doc.firestore(), doc.key(), SnapshotMetadata{/*pending_writes=*/false, @@ -430,7 +429,7 @@ void FirestoreClient::GetDocumentsFromLocalCache( View view(query.query(), query_result.remote_keys()); ViewDocumentChanges view_doc_changes = - view.ComputeDocumentChanges(query_result.documents().underlying_map()); + view.ComputeDocumentChanges(query_result.documents()); ViewChange view_change = view.ApplyChanges(view_doc_changes); HARD_ASSERT( view_change.limbo_changes().empty(), diff --git a/Firestore/core/src/core/in_filter.cc b/Firestore/core/src/core/in_filter.cc index 04677d48c3a..642afcce9ba 100644 --- a/Firestore/core/src/core/in_filter.cc +++ b/Firestore/core/src/core/in_filter.cc @@ -20,22 +20,27 @@ #include #include "Firestore/core/src/model/document.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/util/hard_assert.h" #include "absl/algorithm/container.h" namespace firebase { namespace firestore { namespace core { +using model::Contains; using model::Document; using model::FieldPath; -using model::FieldValue; +using model::IsArray; +using nanopb::SharedMessage; using Operator = Filter::Operator; class InFilter::Rep : public FieldFilter::Rep { public: - Rep(FieldPath field, FieldValue value) + Rep(FieldPath field, SharedMessage value) : FieldFilter::Rep(std::move(field), Operator::In, std::move(value)) { + HARD_ASSERT(IsArray(this->value()), "InFilter expects an ArrayValue"); } Type type() const override { @@ -45,16 +50,16 @@ class InFilter::Rep : public FieldFilter::Rep { bool Matches(const model::Document& doc) const override; }; -InFilter::InFilter(FieldPath field, FieldValue value) - : FieldFilter( - std::make_shared(std::move(field), std::move(value))) { +InFilter::InFilter(const FieldPath& field, + SharedMessage value) + : FieldFilter(std::make_shared(field, std::move(value))) { } bool InFilter::Rep::Matches(const Document& doc) const { - const FieldValue::Array& array_value = value().array_value(); - absl::optional maybe_lhs = doc.field(field()); + const google_firestore_v1_ArrayValue& array_value = value().array_value; + absl::optional maybe_lhs = doc->field(field()); if (!maybe_lhs) return false; - return absl::c_linear_search(array_value, *maybe_lhs); + return Contains(array_value, *maybe_lhs); } } // namespace core diff --git a/Firestore/core/src/core/in_filter.h b/Firestore/core/src/core/in_filter.h index 407a33ff1dc..e8daf238c69 100644 --- a/Firestore/core/src/core/in_filter.h +++ b/Firestore/core/src/core/in_filter.h @@ -19,14 +19,15 @@ #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/field_filter.h" +#include "Firestore/core/src/nanopb/message.h" namespace firebase { namespace firestore { namespace model { class FieldPath; -class FieldValue; } // namespace model namespace core { @@ -36,7 +37,9 @@ namespace core { */ class InFilter : public FieldFilter { public: - InFilter(model::FieldPath field, model::FieldValue value); + /** Creates a new 'in' filter. Takes ownership of `value`. */ + InFilter(const model::FieldPath& field, + nanopb::SharedMessage value); private: class Rep; diff --git a/Firestore/core/src/core/key_field_filter.cc b/Firestore/core/src/core/key_field_filter.cc index 1cf21af8ca0..ea9689037b9 100644 --- a/Firestore/core/src/core/key_field_filter.cc +++ b/Firestore/core/src/core/key_field_filter.cc @@ -21,6 +21,9 @@ #include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_key.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" +#include "Firestore/core/src/util/hard_assert.h" #include "absl/algorithm/container.h" namespace firebase { @@ -30,14 +33,22 @@ namespace core { using model::Document; using model::DocumentKey; using model::FieldPath; -using model::FieldValue; +using model::GetTypeOrder; +using model::TypeOrder; +using nanopb::SharedMessage; using Operator = Filter::Operator; class KeyFieldFilter::Rep : public FieldFilter::Rep { public: - Rep(FieldPath field, Operator op, FieldValue value) + Rep(FieldPath field, + Operator op, + SharedMessage value) : FieldFilter::Rep(std::move(field), op, std::move(value)) { + HARD_ASSERT(GetTypeOrder(this->value()) == TypeOrder::kReference, + "KeyFieldFilter expects a ReferenceValue"); + key_ = DocumentKey::FromName( + nanopb::MakeString(this->value().reference_value)); } Type type() const override { @@ -45,18 +56,19 @@ class KeyFieldFilter::Rep : public FieldFilter::Rep { } bool Matches(const model::Document& doc) const override; + + private: + DocumentKey key_; }; -KeyFieldFilter::KeyFieldFilter(FieldPath field, Operator op, FieldValue value) - : FieldFilter( - std::make_shared(std::move(field), op, std::move(value))) { +KeyFieldFilter::KeyFieldFilter(const FieldPath& field, + Operator op, + SharedMessage value) + : FieldFilter(std::make_shared(field, op, std::move(value))) { } bool KeyFieldFilter::Rep::Matches(const Document& doc) const { - const DocumentKey& lhs_key = doc.key(); - const DocumentKey& rhs_key = value().reference_value().key(); - - return MatchesComparison(lhs_key.CompareTo(rhs_key)); + return MatchesComparison(doc->key().CompareTo(key_)); } } // namespace core diff --git a/Firestore/core/src/core/key_field_filter.h b/Firestore/core/src/core/key_field_filter.h index 6bb9904e9fd..ae8d1b6df19 100644 --- a/Firestore/core/src/core/key_field_filter.h +++ b/Firestore/core/src/core/key_field_filter.h @@ -19,7 +19,9 @@ #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/field_filter.h" +#include "Firestore/core/src/nanopb/message.h" namespace firebase { namespace firestore { @@ -30,9 +32,10 @@ namespace core { */ class KeyFieldFilter : public FieldFilter { public: - KeyFieldFilter(model::FieldPath field, + /** Creates a new document key filter. Takes ownership of `value`. */ + KeyFieldFilter(const model::FieldPath& field, core::Filter::Operator op, - model::FieldValue value); + nanopb::SharedMessage value); private: class Rep; diff --git a/Firestore/core/src/core/key_field_in_filter.cc b/Firestore/core/src/core/key_field_in_filter.cc index e3946ccc3cb..e7590398970 100644 --- a/Firestore/core/src/core/key_field_in_filter.cc +++ b/Firestore/core/src/core/key_field_in_filter.cc @@ -21,6 +21,8 @@ #include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_key.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" #include "absl/algorithm/container.h" namespace firebase { @@ -28,16 +30,21 @@ namespace firestore { namespace core { using model::Document; +using model::DocumentKey; +using model::DocumentKeyHash; using model::FieldPath; -using model::FieldValue; +using model::GetTypeOrder; +using model::IsArray; +using model::TypeOrder; +using nanopb::SharedMessage; using Operator = Filter::Operator; class KeyFieldInFilter::Rep : public FieldFilter::Rep { public: - Rep(FieldPath field, FieldValue value) + Rep(FieldPath field, SharedMessage value) : FieldFilter::Rep(std::move(field), Operator::In, std::move(value)) { - ValidateArrayValue(this->value()); + keys_ = ExtractDocumentKeysFromValue(this->value()); } Type type() const override { @@ -45,37 +52,35 @@ class KeyFieldInFilter::Rep : public FieldFilter::Rep { } bool Matches(const model::Document& doc) const override; + + private: + std::unordered_set keys_; }; -KeyFieldInFilter::KeyFieldInFilter(FieldPath field, FieldValue value) - : FieldFilter( - std::make_shared(std::move(field), std::move(value))) { +KeyFieldInFilter::KeyFieldInFilter( + const FieldPath& field, SharedMessage value) + : FieldFilter(std::make_shared(field, std::move(value))) { } bool KeyFieldInFilter::Rep::Matches(const Document& doc) const { - const FieldValue::Array& array_value = value().array_value(); - return Contains(array_value, doc); -} - -bool KeyFieldInFilter::Contains(const FieldValue::Array& array_value, - const Document& doc) { - for (const auto& rhs : array_value) { - if (doc.key() == rhs.reference_value().key()) { - return true; - } - } - return false; + return keys_.find(doc->key()) != keys_.end(); } -void KeyFieldInFilter::ValidateArrayValue(const FieldValue& value) { - HARD_ASSERT(value.type() == FieldValue::Type::Array, +std::unordered_set +KeyFieldInFilter::ExtractDocumentKeysFromValue( + const google_firestore_v1_Value& value) { + HARD_ASSERT(IsArray(value), "Comparing on key with In/NotIn, but the value was not an Array"); - const FieldValue::Array& array_value = value.array_value(); - for (const auto& ref_value : array_value) { - HARD_ASSERT(ref_value.type() == FieldValue::Type::Reference, + std::unordered_set keys; + const google_firestore_v1_ArrayValue& array_value = value.array_value; + for (pb_size_t i = 0; i < array_value.values_count; ++i) { + HARD_ASSERT(GetTypeOrder(array_value.values[i]) == TypeOrder::kReference, "Comparing on key with In/NotIn, but an array value was not" " a Reference"); + keys.insert(DocumentKey::FromName( + nanopb::MakeString(array_value.values[i].reference_value))); } + return keys; } } // namespace core diff --git a/Firestore/core/src/core/key_field_in_filter.h b/Firestore/core/src/core/key_field_in_filter.h index f12183bab8b..1f83047aff7 100644 --- a/Firestore/core/src/core/key_field_in_filter.h +++ b/Firestore/core/src/core/key_field_in_filter.h @@ -18,10 +18,13 @@ #define FIRESTORE_CORE_SRC_CORE_KEY_FIELD_IN_FILTER_H_ #include +#include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/field_filter.h" #include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/model_fwd.h" +#include "Firestore/core/src/nanopb/message.h" namespace firebase { namespace firestore { @@ -32,15 +35,15 @@ namespace core { */ class KeyFieldInFilter : public FieldFilter { public: - KeyFieldInFilter(model::FieldPath field, model::FieldValue value); + /** Creates a new document keys filter. Takes ownership of `value`. */ + KeyFieldInFilter(const model::FieldPath& field, + nanopb::SharedMessage value); private: class Rep; - static bool Contains(const model::FieldValue::Array& array_value, - const model::Document& doc); - - static void ValidateArrayValue(const model::FieldValue& value); + static std::unordered_set + ExtractDocumentKeysFromValue(const google_firestore_v1_Value& value); friend class KeyFieldNotInFilter; }; diff --git a/Firestore/core/src/core/key_field_not_in_filter.cc b/Firestore/core/src/core/key_field_not_in_filter.cc index 0ede49e8ca0..5c25badcd97 100644 --- a/Firestore/core/src/core/key_field_not_in_filter.cc +++ b/Firestore/core/src/core/key_field_not_in_filter.cc @@ -18,6 +18,7 @@ #include "Firestore/core/src/core/key_field_in_filter.h" #include +#include #include #include "Firestore/core/src/model/document.h" @@ -29,16 +30,18 @@ namespace firestore { namespace core { using model::Document; +using model::DocumentKey; +using model::DocumentKeyHash; using model::FieldPath; -using model::FieldValue; +using nanopb::SharedMessage; using Operator = Filter::Operator; class KeyFieldNotInFilter::Rep : public FieldFilter::Rep { public: - Rep(FieldPath field, FieldValue value) + Rep(FieldPath field, SharedMessage value) : FieldFilter::Rep(std::move(field), Operator::NotIn, std::move(value)) { - KeyFieldInFilter::ValidateArrayValue(this->value()); + keys_ = KeyFieldInFilter::ExtractDocumentKeysFromValue(this->value()); } Type type() const override { @@ -46,16 +49,18 @@ class KeyFieldNotInFilter::Rep : public FieldFilter::Rep { } bool Matches(const model::Document& doc) const override; + + private: + std::unordered_set keys_; }; -KeyFieldNotInFilter::KeyFieldNotInFilter(FieldPath field, FieldValue value) - : FieldFilter( - std::make_shared(std::move(field), std::move(value))) { +KeyFieldNotInFilter::KeyFieldNotInFilter( + const FieldPath& field, SharedMessage value) + : FieldFilter(std::make_shared(field, std::move(value))) { } bool KeyFieldNotInFilter::Rep::Matches(const Document& doc) const { - const FieldValue::Array& array_value = value().array_value(); - return !KeyFieldInFilter::Contains(array_value, doc); + return keys_.find(doc->key()) == keys_.end(); } } // namespace core diff --git a/Firestore/core/src/core/key_field_not_in_filter.h b/Firestore/core/src/core/key_field_not_in_filter.h index f494209bfa3..51ce440bd57 100644 --- a/Firestore/core/src/core/key_field_not_in_filter.h +++ b/Firestore/core/src/core/key_field_not_in_filter.h @@ -19,8 +19,10 @@ #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/field_filter.h" #include "Firestore/core/src/model/model_fwd.h" +#include "Firestore/core/src/nanopb/message.h" namespace firebase { namespace firestore { @@ -31,7 +33,9 @@ namespace core { */ class KeyFieldNotInFilter : public FieldFilter { public: - KeyFieldNotInFilter(model::FieldPath field, model::FieldValue value); + /** Creates a new document keys not-in filter. Takes ownership of `value`. */ + KeyFieldNotInFilter(const model::FieldPath& field, + nanopb::SharedMessage value); private: class Rep; diff --git a/Firestore/core/src/core/not_in_filter.cc b/Firestore/core/src/core/not_in_filter.cc index ee3c6cc17be..612e6bc9004 100644 --- a/Firestore/core/src/core/not_in_filter.cc +++ b/Firestore/core/src/core/not_in_filter.cc @@ -20,22 +20,28 @@ #include #include "Firestore/core/src/model/document.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/util/hard_assert.h" #include "absl/algorithm/container.h" namespace firebase { namespace firestore { namespace core { +using model::Contains; using model::Document; using model::FieldPath; -using model::FieldValue; +using model::IsArray; +using model::NullValue; +using nanopb::SharedMessage; using Operator = Filter::Operator; class NotInFilter::Rep : public FieldFilter::Rep { public: - Rep(FieldPath field, FieldValue value) + Rep(FieldPath field, SharedMessage value) : FieldFilter::Rep(std::move(field), Operator::NotIn, std::move(value)) { + HARD_ASSERT(IsArray(this->value()), "NotInFilter expects an ArrayValue"); } Type type() const override { @@ -45,18 +51,18 @@ class NotInFilter::Rep : public FieldFilter::Rep { bool Matches(const model::Document& doc) const override; }; -NotInFilter::NotInFilter(FieldPath field, FieldValue value) - : FieldFilter( - std::make_shared(std::move(field), std::move(value))) { +NotInFilter::NotInFilter(const FieldPath& field, + SharedMessage value) + : FieldFilter(std::make_shared(field, std::move(value))) { } bool NotInFilter::Rep::Matches(const Document& doc) const { - const FieldValue::Array& array_value = value().array_value(); - if (absl::c_linear_search(array_value, FieldValue::Null())) { + const google_firestore_v1_ArrayValue& array_value = value().array_value; + if (Contains(array_value, *NullValue())) { return false; } - absl::optional maybe_lhs = doc.field(field()); - return maybe_lhs && !absl::c_linear_search(array_value, *maybe_lhs); + absl::optional maybe_lhs = doc->field(field()); + return maybe_lhs && !Contains(array_value, *maybe_lhs); } } // namespace core diff --git a/Firestore/core/src/core/not_in_filter.h b/Firestore/core/src/core/not_in_filter.h index 04376bbfb63..723b63bbe26 100644 --- a/Firestore/core/src/core/not_in_filter.h +++ b/Firestore/core/src/core/not_in_filter.h @@ -19,8 +19,10 @@ #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/field_filter.h" #include "Firestore/core/src/model/model_fwd.h" +#include "Firestore/core/src/nanopb/message.h" namespace firebase { namespace firestore { @@ -31,7 +33,9 @@ namespace core { */ class NotInFilter : public FieldFilter { public: - NotInFilter(model::FieldPath field, model::FieldValue value); + /** Creates a new not-in filter. Takes ownership of `value`. */ + NotInFilter(const model::FieldPath& field, + nanopb::SharedMessage value); private: class Rep; diff --git a/Firestore/core/src/core/order_by.cc b/Firestore/core/src/core/order_by.cc index fccfae6ac8d..cb0e4d6c316 100644 --- a/Firestore/core/src/core/order_by.cc +++ b/Firestore/core/src/core/order_by.cc @@ -19,7 +19,7 @@ #include #include "Firestore/core/src/model/document.h" -#include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/model/value_util.h" #include "Firestore/core/src/util/string_format.h" #include "absl/strings/str_cat.h" @@ -29,20 +29,19 @@ namespace core { using model::Document; using model::FieldPath; -using model::FieldValue; using util::ComparisonResult; ComparisonResult OrderBy::Compare(const Document& lhs, const Document& rhs) const { ComparisonResult result; if (field_ == FieldPath::KeyFieldPath()) { - result = lhs.key().CompareTo(rhs.key()); + result = lhs->key().CompareTo(rhs->key()); } else { - absl::optional value1 = lhs.field(field_); - absl::optional value2 = rhs.field(field_); + absl::optional value1 = lhs->field(field_); + absl::optional value2 = rhs->field(field_); HARD_ASSERT(value1.has_value() && value2.has_value(), "Trying to compare documents on fields that don't exist."); - result = value1->CompareTo(*value2); + result = model::Compare(*value1, *value2); } return direction_.ApplyTo(result); diff --git a/Firestore/core/src/core/query.cc b/Firestore/core/src/core/query.cc index 38e7a007236..5254440cf87 100644 --- a/Firestore/core/src/core/query.cc +++ b/Firestore/core/src/core/query.cc @@ -204,13 +204,12 @@ Query Query::WithLimitToLast(int32_t limit) const { Query Query::StartingAt(Bound bound) const { return Query(path_, collection_group_, filters_, explicit_order_bys_, limit_, - limit_type_, std::make_shared(std::move(bound)), end_at_); + limit_type_, std::move(bound), end_at_); } Query Query::EndingAt(Bound bound) const { return Query(path_, collection_group_, filters_, explicit_order_bys_, limit_, - limit_type_, start_at_, - std::make_shared(std::move(bound))); + limit_type_, start_at_, std::move(bound)); } Query Query::AsCollectionQueryAtPath(ResourcePath path) const { @@ -221,16 +220,16 @@ Query Query::AsCollectionQueryAtPath(ResourcePath path) const { // MARK: - Matching bool Query::Matches(const Document& doc) const { - return MatchesPathAndCollectionGroup(doc) && MatchesOrderBy(doc) && - MatchesFilters(doc) && MatchesBounds(doc); + return doc->is_found_document() && MatchesPathAndCollectionGroup(doc) && + MatchesOrderBy(doc) && MatchesFilters(doc) && MatchesBounds(doc); } bool Query::MatchesPathAndCollectionGroup(const Document& doc) const { - const ResourcePath& doc_path = doc.key().path(); + const ResourcePath& doc_path = doc->key().path(); if (collection_group_) { // NOTE: path_ is currently always empty since we don't expose Collection // Group queries rooted at a document path yet. - return doc.key().HasCollectionId(*collection_group_) && + return doc->key().HasCollectionId(*collection_group_) && path_.IsPrefixOf(doc_path); } else if (DocumentKey::IsDocumentKey(path_)) { // Exact match for document queries. @@ -253,7 +252,7 @@ bool Query::MatchesOrderBy(const Document& doc) const { const FieldPath& field_path = order_by.field(); // order by key always matches if (field_path != FieldPath::KeyFieldPath() && - doc.field(field_path) == absl::nullopt) { + doc->field(field_path) == absl::nullopt) { return false; } } @@ -323,14 +322,14 @@ const Target& Query::ToTarget() const& { } // We need to swap the cursors to match the now-flipped query ordering. - auto new_start_at = - (end_at_ != nullptr) - ? std::make_shared(end_at_->position(), !end_at_->before()) - : nullptr; - auto new_end_at = (start_at_ != nullptr) - ? std::make_shared(start_at_->position(), - !start_at_->before()) - : nullptr; + auto new_start_at = end_at_ + ? absl::optional{Bound::FromValue( + end_at_->position(), !end_at_->before())} + : absl::nullopt; + auto new_end_at = start_at_ + ? absl::optional{Bound::FromValue( + start_at_->position(), !start_at_->before())} + : absl::nullopt; Target target(path(), collection_group(), filters(), new_order_bys, limit_, new_start_at, new_end_at); diff --git a/Firestore/core/src/core/query.h b/Firestore/core/src/core/query.h index ee801b57112..0d27f8c62ea 100644 --- a/Firestore/core/src/core/query.h +++ b/Firestore/core/src/core/query.h @@ -66,8 +66,8 @@ class Query { OrderByList explicit_order_bys, int32_t limit, LimitType limit_type, - std::shared_ptr start_at, - std::shared_ptr end_at) + absl::optional start_at, + absl::optional end_at) : path_(std::move(path)), collection_group_(std::move(collection_group)), filters_(std::move(filters)), @@ -158,11 +158,11 @@ class Query { int32_t limit() const; - const std::shared_ptr& start_at() const { + const absl::optional& start_at() const { return start_at_; } - const std::shared_ptr& end_at() const { + const absl::optional& end_at() const { return end_at_; } @@ -270,8 +270,8 @@ class Query { int32_t limit_ = Target::kNoLimit; LimitType limit_type_ = LimitType::None; - std::shared_ptr start_at_; - std::shared_ptr end_at_; + absl::optional start_at_; + absl::optional end_at_; // The corresponding Target of this Query instance. mutable std::shared_ptr memoized_target; diff --git a/Firestore/core/src/core/sync_engine.cc b/Firestore/core/src/core/sync_engine.cc index ff67abeb9dd..1a018c56165 100644 --- a/Firestore/core/src/core/sync_engine.cc +++ b/Firestore/core/src/core/sync_engine.cc @@ -30,10 +30,9 @@ #include "Firestore/core/src/local/target_data.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/document_key_set.h" -#include "Firestore/core/src/model/document_map.h" #include "Firestore/core/src/model/document_set.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/mutation_batch_result.h" -#include "Firestore/core/src/model/no_document.h" #include "Firestore/core/src/util/async_queue.h" #include "Firestore/core/src/util/log.h" #include "Firestore/core/src/util/status.h" @@ -60,11 +59,11 @@ using local::TargetData; using model::BatchId; using model::DocumentKey; using model::DocumentKeySet; +using model::DocumentMap; using model::DocumentUpdateMap; using model::kBatchIdUnknown; using model::ListenSequenceNumber; -using model::MaybeDocumentMap; -using model::NoDocument; +using model::MutableDocument; using model::SnapshotVersion; using model::TargetId; using remote::RemoteEvent; @@ -141,7 +140,7 @@ ViewSnapshot SyncEngine::InitializeViewAndComputeSnapshot(const Query& query, View view(query, query_result.remote_keys()); ViewDocumentChanges view_doc_changes = - view.ComputeDocumentChanges(query_result.documents().underlying_map()); + view.ComputeDocumentChanges(query_result.documents()); ViewChange view_change = view.ApplyChanges(view_doc_changes, synthesized_current_change); UpdateTrackedLimboDocuments(view_change.limbo_changes(), target_id); @@ -259,7 +258,7 @@ void SyncEngine::HandleCredentialChange(const auth::User& user) { "'waitForPendingWrites' callback is cancelled due to a user change."); // Notify local store and emit any resulting events from swapping out the // mutation queue. - MaybeDocumentMap changes = local_store_->HandleUserChange(user); + DocumentMap changes = local_store_->HandleUserChange(user); EmitNewSnapshotsAndNotifyLocalStore(changes, absl::nullopt); } @@ -303,7 +302,7 @@ void SyncEngine::ApplyRemoteEvent(const RemoteEvent& remote_event) { } } - MaybeDocumentMap changes = local_store_->ApplyRemoteEvent(remote_event); + DocumentMap changes = local_store_->ApplyRemoteEvent(remote_event); EmitNewSnapshotsAndNotifyLocalStore(changes, remote_event); } @@ -325,8 +324,8 @@ void SyncEngine::HandleRejectedListen(TargetId target_id, Status error) { // kind of a hack. Ideally, we would have a method in the local store to // purge a document. However, it would be tricky to keep all of the local // store's invariants with another method. - NoDocument doc(limbo_key, SnapshotVersion::None(), - /* has_committed_mutations= */ false); + MutableDocument doc = + MutableDocument::NoDocument(limbo_key, SnapshotVersion::None()); // Explicitly instantiate these to work around a bug in the default // constructor of the std::unordered_map that comes with GCC 4.8. Without @@ -348,7 +347,7 @@ void SyncEngine::HandleRejectedListen(TargetId target_id, Status error) { } void SyncEngine::HandleSuccessfulWrite( - const model::MutationBatchResult& batch_result) { + model::MutationBatchResult batch_result) { AssertCallbackExists("HandleSuccessfulWrite"); // The local store may or may not be able to apply the write result and @@ -359,7 +358,7 @@ void SyncEngine::HandleSuccessfulWrite( TriggerPendingWriteCallbacks(batch_result.batch().batch_id()); - MaybeDocumentMap changes = local_store_->AcknowledgeBatch(batch_result); + DocumentMap changes = local_store_->AcknowledgeBatch(batch_result); EmitNewSnapshotsAndNotifyLocalStore(changes, absl::nullopt); } @@ -367,7 +366,7 @@ void SyncEngine::HandleRejectedWrite( firebase::firestore::model::BatchId batch_id, Status error) { AssertCallbackExists("HandleRejectedWrite"); - MaybeDocumentMap changes = local_store_->RejectBatch(batch_id); + DocumentMap changes = local_store_->RejectBatch(batch_id); if (!changes.empty() && ErrorIsInteresting(error)) { const DocumentKey& min_key = changes.min()->first; @@ -464,7 +463,7 @@ void SyncEngine::FailOutstandingPendingWriteCallbacks( } void SyncEngine::EmitNewSnapshotsAndNotifyLocalStore( - const MaybeDocumentMap& changes, + const DocumentMap& changes, const absl::optional& maybe_remote_event) { std::vector new_snapshots; std::vector document_changes_in_all_views; @@ -479,8 +478,8 @@ void SyncEngine::EmitNewSnapshotsAndNotifyLocalStore( // any good docs that had been past the limit. QueryResult query_result = local_store_->ExecuteQuery( query_view->query(), /* use_previous_results= */ false); - view_doc_changes = view.ComputeDocumentChanges( - query_result.documents().underlying_map(), view_doc_changes); + view_doc_changes = view.ComputeDocumentChanges(query_result.documents(), + view_doc_changes); } absl::optional target_changes; @@ -640,8 +639,7 @@ void SyncEngine::LoadBundle(std::shared_ptr reader, return; } - util::StatusOr changes = - maybe_loader.value().ApplyChanges(); + util::StatusOr changes = maybe_loader.value().ApplyChanges(); if (!changes.ok()) { LOG_WARN("Failed to ApplyChanges() for bundle elements with error %s", changes.status().error_message()); diff --git a/Firestore/core/src/core/sync_engine.h b/Firestore/core/src/core/sync_engine.h index 8155154ef67..bb767302fd1 100644 --- a/Firestore/core/src/core/sync_engine.h +++ b/Firestore/core/src/core/sync_engine.h @@ -144,8 +144,7 @@ class SyncEngine : public remote::RemoteStoreCallback, public QueryEventSource { void ApplyRemoteEvent(const remote::RemoteEvent& remote_event) override; void HandleRejectedListen(model::TargetId target_id, util::Status error) override; - void HandleSuccessfulWrite( - const model::MutationBatchResult& batch_result) override; + void HandleSuccessfulWrite(model::MutationBatchResult batch_result) override; void HandleRejectedWrite(model::BatchId batch_id, util::Status error) override; void HandleOnlineStateChange(model::OnlineState online_state) override; @@ -236,7 +235,7 @@ class SyncEngine : public remote::RemoteStoreCallback, public QueryEventSource { void RemoveLimboTarget(const model::DocumentKey& key); void EmitNewSnapshotsAndNotifyLocalStore( - const model::MaybeDocumentMap& changes, + const model::DocumentMap& changes, const absl::optional& maybe_remote_event); /** Updates the limbo document state for the given target_id. */ diff --git a/Firestore/core/src/core/target.cc b/Firestore/core/src/core/target.cc index 2cb4afe7341..7fa3dfadc62 100644 --- a/Firestore/core/src/core/target.cc +++ b/Firestore/core/src/core/target.cc @@ -96,9 +96,8 @@ bool operator==(const Target& lhs, const Target& rhs) { return lhs.path() == rhs.path() && util::Equals(lhs.collection_group(), rhs.collection_group()) && lhs.filters() == rhs.filters() && lhs.order_bys() == rhs.order_bys() && - lhs.limit() == rhs.limit() && - util::Equals(lhs.start_at(), rhs.start_at()) && - util::Equals(lhs.end_at(), rhs.end_at()); + lhs.limit() == rhs.limit() && lhs.start_at() == rhs.start_at() && + lhs.end_at() == rhs.end_at(); } } // namespace core diff --git a/Firestore/core/src/core/target.h b/Firestore/core/src/core/target.h index 7c898a70d7a..fc8154a3f7a 100644 --- a/Firestore/core/src/core/target.h +++ b/Firestore/core/src/core/target.h @@ -81,11 +81,11 @@ class Target { return limit_; } - const std::shared_ptr& start_at() const { + const absl::optional& start_at() const { return start_at_; } - const std::shared_ptr& end_at() const { + const absl::optional& end_at() const { return end_at_; } @@ -111,8 +111,8 @@ class Target { FilterList filters, OrderByList order_bys, int32_t limit, - std::shared_ptr start_at, - std::shared_ptr end_at) + absl::optional start_at, + absl::optional end_at) : path_(std::move(path)), collection_group_(std::move(collection_group)), filters_(std::move(filters)), @@ -130,8 +130,8 @@ class Target { FilterList filters_; OrderByList order_bys_; int32_t limit_ = kNoLimit; - std::shared_ptr start_at_; - std::shared_ptr end_at_; + absl::optional start_at_; + absl::optional end_at_; mutable std::string canonical_id_; }; diff --git a/Firestore/core/src/core/transaction.cc b/Firestore/core/src/core/transaction.cc index 7b0d8d240a8..b18eff225b7 100644 --- a/Firestore/core/src/core/transaction.cc +++ b/Firestore/core/src/core/transaction.cc @@ -23,6 +23,7 @@ #include "Firestore/core/include/firebase/firestore/firestore_errors.h" #include "Firestore/core/src/core/user_data.h" #include "Firestore/core/src/model/delete_mutation.h" +#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/verify_mutation.h" #include "Firestore/core/src/remote/datastore.h" #include "Firestore/core/src/util/hard_assert.h" @@ -31,9 +32,9 @@ using firebase::firestore::Error; using firebase::firestore::core::ParsedSetData; using firebase::firestore::core::ParsedUpdateData; using firebase::firestore::model::DeleteMutation; +using firebase::firestore::model::Document; using firebase::firestore::model::DocumentKey; using firebase::firestore::model::DocumentKeyHash; -using firebase::firestore::model::MaybeDocument; using firebase::firestore::model::Mutation; using firebase::firestore::model::Precondition; using firebase::firestore::model::SnapshotVersion; @@ -50,20 +51,20 @@ Transaction::Transaction(Datastore* datastore) : datastore_{NOT_NULL(datastore)} { } -Status Transaction::RecordVersion(const MaybeDocument& doc) { +Status Transaction::RecordVersion(const Document& doc) { SnapshotVersion doc_version; - if (doc.is_document()) { - doc_version = doc.version(); - } else if (doc.is_no_document()) { + if (doc->is_found_document()) { + doc_version = doc->version(); + } else if (doc->is_no_document()) { // For deleted docs, we must record an explicit no version to build the // right precondition when writing. doc_version = SnapshotVersion::None(); } else { - HARD_FAIL("Unexpected document type in transaction: %s", doc.type()); + HARD_FAIL("Unexpected document type in transaction: %s", doc.ToString()); } - absl::optional existing_version = GetVersion(doc.key()); + absl::optional existing_version = GetVersion(doc->key()); if (existing_version.has_value()) { if (doc_version != existing_version.value()) { // This transaction will fail no matter what. @@ -72,7 +73,7 @@ Status Transaction::RecordVersion(const MaybeDocument& doc) { } return Status::OK(); } else { - read_versions_[doc.key()] = doc_version; + read_versions_[doc->key()] = doc_version; return Status::OK(); } } @@ -90,15 +91,15 @@ void Transaction::Lookup(const std::vector& keys, } datastore_->LookupDocuments( - keys, [this, callback]( - const StatusOr>& maybe_documents) { + keys, + [this, callback](const StatusOr>& maybe_documents) { if (!maybe_documents.ok()) { callback(maybe_documents.status()); return; } const auto& documents = maybe_documents.ValueOrDie(); - for (const MaybeDocument& doc : documents) { + for (const Document& doc : documents) { Status record_error = RecordVersion(doc); if (!record_error.ok()) { callback(record_error); diff --git a/Firestore/core/src/core/transaction.h b/Firestore/core/src/core/transaction.h index 98239b3d215..82b941c39bf 100644 --- a/Firestore/core/src/core/transaction.h +++ b/Firestore/core/src/core/transaction.h @@ -36,6 +36,7 @@ namespace firestore { namespace model { class Precondition; +class Document; } // namespace model namespace remote { @@ -49,8 +50,8 @@ class ParsedUpdateData; class Transaction { public: - using LookupCallback = std::function>&)>; + using LookupCallback = + std::function>&)>; Transaction() = default; explicit Transaction(remote::Datastore* datastore); @@ -105,7 +106,7 @@ class Transaction { * error. When the transaction is committed, the versions recorded will be set * as preconditions on the writes sent to the backend. */ - util::Status RecordVersion(const model::MaybeDocument& doc); + util::Status RecordVersion(const model::Document& doc); /** Stores mutations to be written when `Commit` is called. */ void WriteMutations(std::vector&& mutations); diff --git a/Firestore/core/src/core/user_data.cc b/Firestore/core/src/core/user_data.cc index f1b32bf4eaf..0a2a771e517 100644 --- a/Firestore/core/src/core/user_data.cc +++ b/Firestore/core/src/core/user_data.cc @@ -18,7 +18,8 @@ #include -#include "Firestore/core/src/model/maybe_document.h" +#include "Firestore/core/src/model/document.h" +#include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/mutation.h" #include "Firestore/core/src/model/patch_mutation.h" #include "Firestore/core/src/model/set_mutation.h" @@ -72,6 +73,7 @@ void ParseAccumulator::AddToFieldMask(FieldPath field_path) { void ParseAccumulator::AddToFieldTransforms( FieldPath field_path, TransformOperation transform_operation) { + // TODO(mrschmidt): Validate that the paths are unique field_transforms_.emplace_back(std::move(field_path), std::move(transform_operation)); } diff --git a/Firestore/core/src/core/user_data.h b/Firestore/core/src/core/user_data.h index ed7f47052aa..eb20250e379 100644 --- a/Firestore/core/src/core/user_data.h +++ b/Firestore/core/src/core/user_data.h @@ -26,7 +26,7 @@ #include "Firestore/core/src/model/field_mask.h" #include "Firestore/core/src/model/field_path.h" #include "Firestore/core/src/model/field_transform.h" -#include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/model/object_value.h" namespace firebase { namespace firestore { @@ -34,6 +34,7 @@ namespace model { class Precondition; class Mutation; +class DocumentKey; } // namespace model diff --git a/Firestore/core/src/core/view.cc b/Firestore/core/src/core/view.cc index 33a82919dac..c80adf517dd 100644 --- a/Firestore/core/src/core/view.cc +++ b/Firestore/core/src/core/view.cc @@ -20,7 +20,6 @@ #include "Firestore/core/src/core/target.h" #include "Firestore/core/src/model/document_set.h" -#include "Firestore/core/src/model/field_value.h" namespace firebase { namespace firestore { @@ -29,9 +28,8 @@ namespace core { using model::Document; using model::DocumentKey; using model::DocumentKeySet; +using model::DocumentMap; using model::DocumentSet; -using model::MaybeDocument; -using model::MaybeDocumentMap; using model::OnlineState; using remote::TargetChange; using util::ComparisonResult; @@ -95,7 +93,7 @@ ComparisonResult View::Compare(const Document& lhs, const Document& rhs) const { } ViewDocumentChanges View::ComputeDocumentChanges( - const MaybeDocumentMap& doc_changes, + const DocumentMap& doc_changes, const absl::optional& previous_changes) const { DocumentViewChangeSet change_set; if (previous_changes) { @@ -132,21 +130,11 @@ ViewDocumentChanges View::ComputeDocumentChanges( for (const auto& kv : doc_changes) { const DocumentKey& key = kv.first; - const MaybeDocument& maybe_new_doc = kv.second; absl::optional old_doc = old_document_set.GetDocument(key); - absl::optional new_doc; - if (maybe_new_doc.is_document()) { - new_doc = Document(maybe_new_doc); - } - if (new_doc) { - HARD_ASSERT(key == new_doc->key(), - "Mismatching key in document changes: %s != %s", - key.ToString(), new_doc->key().ToString()); - if (!query_.Matches(*new_doc)) { - new_doc = absl::nullopt; - } - } + absl::optional new_doc = query_.Matches(kv.second) + ? absl::optional{kv.second} + : absl::nullopt; bool old_doc_had_pending_mutations = old_doc && old_mutated_keys.contains(key); @@ -154,14 +142,14 @@ ViewDocumentChanges View::ComputeDocumentChanges( // We only consider committed mutations for documents that were mutated // during the lifetime of the view. bool new_doc_has_pending_mutations = - new_doc && (new_doc->has_local_mutations() || + new_doc && ((*new_doc)->has_local_mutations() || (old_mutated_keys.contains(key) && - new_doc->has_committed_mutations())); + (*new_doc)->has_committed_mutations())); bool change_applied = false; // Calculate change if (old_doc && new_doc) { - bool docs_equal = old_doc->data() == new_doc->data(); + bool docs_equal = (*old_doc)->value() == (*new_doc)->value(); if (!docs_equal) { if (!ShouldWaitForSyncedDocument(*new_doc, *old_doc)) { change_set.AddChange( @@ -208,7 +196,7 @@ ViewDocumentChanges View::ComputeDocumentChanges( if (change_applied) { if (new_doc) { new_document_set = new_document_set.insert(new_doc); - if (new_doc->has_local_mutations()) { + if ((*new_doc)->has_local_mutations()) { new_mutated_keys = new_mutated_keys.insert(key); } else { new_mutated_keys = new_mutated_keys.erase(key); @@ -229,8 +217,8 @@ ViewDocumentChanges View::ComputeDocumentChanges( query_.has_limit_to_first() ? new_document_set.GetLastDocument() : new_document_set.GetFirstDocument(); const Document& old_doc = *found; - new_document_set = new_document_set.erase(old_doc.key()); - new_mutated_keys = new_mutated_keys.erase(old_doc.key()); + new_document_set = new_document_set.erase(old_doc->key()); + new_mutated_keys = new_mutated_keys.erase(old_doc->key()); change_set.AddChange( DocumentViewChange{old_doc, DocumentViewChange::Type::Removed}); } @@ -253,8 +241,9 @@ bool View::ShouldWaitForSyncedDocument(const Document& new_doc, // `has_pending_writes` and the final state of the document) instead of three // (one with `has_pending_writes`, the modified document with // `has_pending_writes` and the final state of the document). - return (old_doc.has_local_mutations() && new_doc.has_committed_mutations() && - !new_doc.has_local_mutations()); + return (old_doc->has_local_mutations() && + new_doc->has_committed_mutations() && + !new_doc->has_local_mutations()); } ViewChange View::ApplyChanges(const ViewDocumentChanges& doc_changes) { @@ -341,7 +330,7 @@ bool View::ShouldBeInLimbo(const DocumentKey& key) const { // doesn't know that it's part of the query. So don't put it in limbo. // TODO(klimt): Ideally, we would only consider changes that might actually // affect this specific query. - if (document_set_.GetDocument(key)->has_local_mutations()) { + if ((*document_set_.GetDocument(key))->has_local_mutations()) { return false; } // Everything else is in limbo. @@ -383,8 +372,8 @@ std::vector View::UpdateLimboDocuments() { DocumentKeySet old_limbo_documents = std::move(limbo_documents_); limbo_documents_ = DocumentKeySet{}; for (const Document& doc : document_set_) { - if (ShouldBeInLimbo(doc.key())) { - limbo_documents_ = limbo_documents_.insert(doc.key()); + if (ShouldBeInLimbo(doc->key())) { + limbo_documents_ = limbo_documents_.insert(doc->key()); } } diff --git a/Firestore/core/src/core/view.h b/Firestore/core/src/core/view.h index f73f0dec010..b3cdbce69fc 100644 --- a/Firestore/core/src/core/view.h +++ b/Firestore/core/src/core/view.h @@ -157,7 +157,7 @@ class View { * @return a new set of docs, changes, and refill flag. */ core::ViewDocumentChanges ComputeDocumentChanges( - const model::MaybeDocumentMap& doc_changes, + const model::DocumentMap& doc_changes, const absl::optional& previous_changes = absl::nullopt) const; diff --git a/Firestore/core/src/core/view_snapshot.cc b/Firestore/core/src/core/view_snapshot.cc index 9245a6ed520..4a1aa499a0b 100644 --- a/Firestore/core/src/core/view_snapshot.cc +++ b/Firestore/core/src/core/view_snapshot.cc @@ -59,7 +59,7 @@ bool operator==(const DocumentViewChange& lhs, const DocumentViewChange& rhs) { // DocumentViewChangeSet void DocumentViewChangeSet::AddChange(DocumentViewChange&& change) { - const DocumentKey& key = change.document().key(); + const DocumentKey& key = change.document()->key(); auto old_change_iter = change_map_.find(key); if (old_change_iter == change_map_.end()) { change_map_ = change_map_.insert(key, change); diff --git a/Firestore/core/src/local/leveldb_migrations.cc b/Firestore/core/src/local/leveldb_migrations.cc index 26c0ed07f98..1ade190b723 100644 --- a/Firestore/core/src/local/leveldb_migrations.cc +++ b/Firestore/core/src/local/leveldb_migrations.cc @@ -23,11 +23,14 @@ #include "Firestore/Protos/nanopb/firestore/local/target.nanopb.h" #include "Firestore/core/src/local/leveldb_key.h" #include "Firestore/core/src/local/memory_index_manager.h" +#include "Firestore/core/src/local/target_data.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/types.h" #include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/nanopb/reader.h" #include "Firestore/core/src/nanopb/writer.h" +#include "Firestore/core/src/util/log.h" +#include "Firestore/core/src/util/statusor.h" #include "absl/strings/match.h" namespace firebase { @@ -61,8 +64,9 @@ using nanopb::StringReader; * has a sentinel row with a sequence number. * * Migration 5 drops held write acks. * * Migration 6 populates the collection_parents index. + * * Migration 7 rewrites query_targets canonical ids in new format. */ -const LevelDbMigrations::SchemaVersion kSchemaVersion = 6; +const LevelDbMigrations::SchemaVersion kSchemaVersion = 7; /** * Save the given version number as the current version of the schema of the @@ -299,6 +303,81 @@ void EnsureCollectionParentsIndex(leveldb::DB* db) { transaction.Commit(); } +/** + * Returns a `TargetData` by reading the `targets` table, using the given key + * for `query_targets` as a foreign key. + */ +util::StatusOr ReadTargetData( + const LevelDbQueryTargetKey& query_target_key, + const LocalSerializer& serializer, + LevelDbTransaction& transaction) { + auto target_it = transaction.NewIterator(); + const auto& target_key = LevelDbTargetKey::Key(query_target_key.target_id()); + target_it->Seek(target_key); + if (!target_it->Valid()) { + return util::Status( + kErrorNotFound, + util::StringFormat( + "Dangling query-target reference found: seeking %s found %s", + DescribeKey(target_key), DescribeKey(target_it))); + } + + StringReader reader{target_it->value()}; + auto message = Message::TryParse(&reader); + if (!reader.ok()) { + return util::Status(kErrorDataLoss, + util::StringFormat("Target proto failed to parse: %s", + reader.status().ToString())); + } + auto target_data = serializer.DecodeTargetData(&reader, *message); + if (!reader.ok()) { + return util::Status( + kErrorDataLoss, + util::StringFormat("Target failed to parse: %s, message: %s", + reader.status().ToString(), message.ToString())); + } + + return target_data; +} + +/** + * Migration 7. + * + * Rewrites targets canonical IDs with new format. + */ +void RewriteTargetsCanonicalIds(leveldb::DB* db, + const LocalSerializer& serializer) { + LevelDbTransaction transaction(db, "Rewrite Targets Canonical Ids"); + + std::string query_targets_prefix = LevelDbQueryTargetKey::KeyPrefix(); + auto it = transaction.NewIterator(); + it->Seek(query_targets_prefix); + LevelDbQueryTargetKey query_target_key; + for (; it->Valid() && absl::StartsWith(it->key(), query_targets_prefix); + it->Next()) { + HARD_ASSERT(query_target_key.Decode(it->key()), + "Failed to decode query_targets key"); + + util::StatusOr target_data = + ReadTargetData(query_target_key, serializer, transaction); + if (!target_data.ok()) { + LOG_WARN("Reading target data failed: %s", + target_data.status().error_message()); + continue; + } + + auto new_key = LevelDbQueryTargetKey::Key( + target_data.ValueOrDie().target().CanonicalId(), + target_data.ValueOrDie().target_id()); + + transaction.Delete(it->key()); + std::string empty_buffer; + transaction.Put(new_key, empty_buffer); + } + + transaction.Commit(); +} + } // namespace LevelDbMigrations::SchemaVersion LevelDbMigrations::ReadSchemaVersion( @@ -317,12 +396,14 @@ LevelDbMigrations::SchemaVersion LevelDbMigrations::ReadSchemaVersion( } } -void LevelDbMigrations::RunMigrations(leveldb::DB* db) { - RunMigrations(db, kSchemaVersion); +void LevelDbMigrations::RunMigrations(leveldb::DB* db, + const LocalSerializer& serializer) { + RunMigrations(db, kSchemaVersion, serializer); } void LevelDbMigrations::RunMigrations(leveldb::DB* db, - SchemaVersion to_version) { + SchemaVersion to_version, + const LocalSerializer& serializer) { SchemaVersion from_version = ReadSchemaVersion(db); // If this is a downgrade, just save the downgrade version so we can // detect it when we go to upgrade again, allowing us to rerun the @@ -352,6 +433,10 @@ void LevelDbMigrations::RunMigrations(leveldb::DB* db, if (from_version < 6 && to_version >= 6) { EnsureCollectionParentsIndex(db); } + + if (from_version < 7 && to_version >= 7) { + RewriteTargetsCanonicalIds(db, serializer); + } } } // namespace local diff --git a/Firestore/core/src/local/leveldb_migrations.h b/Firestore/core/src/local/leveldb_migrations.h index 2f34178480c..69b688ebc97 100644 --- a/Firestore/core/src/local/leveldb_migrations.h +++ b/Firestore/core/src/local/leveldb_migrations.h @@ -40,13 +40,15 @@ class LevelDbMigrations { * Runs any migrations needed to bring the given database up to the current * schema version */ - static void RunMigrations(leveldb::DB* db); + static void RunMigrations(leveldb::DB* db, const LocalSerializer& serializer); /** * Runs any migrations needed to bring the given database up to the given * schema version */ - static void RunMigrations(leveldb::DB* db, SchemaVersion version); + static void RunMigrations(leveldb::DB* db, + SchemaVersion version, + const LocalSerializer& serializer); }; } // namespace local diff --git a/Firestore/core/src/local/leveldb_persistence.cc b/Firestore/core/src/local/leveldb_persistence.cc index c10fd02e486..30463ef567f 100644 --- a/Firestore/core/src/local/leveldb_persistence.cc +++ b/Firestore/core/src/local/leveldb_persistence.cc @@ -89,7 +89,7 @@ StatusOr> LevelDbPersistence::Create( if (!created.ok()) return created.status(); std::unique_ptr db = std::move(created).ValueOrDie(); - LevelDbMigrations::RunMigrations(db.get()); + LevelDbMigrations::RunMigrations(db.get(), serializer); LevelDbTransaction transaction(db.get(), "Start LevelDB"); std::set users = CollectUserSet(&transaction); diff --git a/Firestore/core/src/local/leveldb_remote_document_cache.cc b/Firestore/core/src/local/leveldb_remote_document_cache.cc index 689419b0ef3..63fe84b3be2 100644 --- a/Firestore/core/src/local/leveldb_remote_document_cache.cc +++ b/Firestore/core/src/local/leveldb_remote_document_cache.cc @@ -26,7 +26,7 @@ #include "Firestore/core/src/local/leveldb_persistence.h" #include "Firestore/core/src/local/local_serializer.h" #include "Firestore/core/src/model/document_key_set.h" -#include "Firestore/core/src/model/document_map.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/nanopb/reader.h" #include "Firestore/core/src/util/background_queue.h" @@ -42,13 +42,10 @@ namespace { using core::Query; using leveldb::Status; -using model::Document; using model::DocumentKey; using model::DocumentKeySet; -using model::DocumentMap; -using model::MaybeDocument; -using model::MaybeDocumentMap; -using model::OptionalMaybeDocumentMap; +using model::MutableDocument; +using model::MutableDocumentMap; using model::ResourcePath; using model::SnapshotVersion; using nanopb::Message; @@ -105,7 +102,7 @@ LevelDbRemoteDocumentCache::LevelDbRemoteDocumentCache( // Out of line because of unique_ptrs to incomplete types. LevelDbRemoteDocumentCache::~LevelDbRemoteDocumentCache() = default; -void LevelDbRemoteDocumentCache::Add(const MaybeDocument& document, +void LevelDbRemoteDocumentCache::Add(const MutableDocument& document, const SnapshotVersion& read_time) { const DocumentKey& key = document.key(); const ResourcePath& path = key.path(); @@ -127,13 +124,12 @@ void LevelDbRemoteDocumentCache::Remove(const DocumentKey& key) { db_->current_transaction()->Delete(ldb_key); } -absl::optional LevelDbRemoteDocumentCache::Get( - const DocumentKey& key) { +MutableDocument LevelDbRemoteDocumentCache::Get(const DocumentKey& key) { std::string ldb_key = LevelDbRemoteDocumentKey::Key(key); std::string value; Status status = db_->current_transaction()->Get(ldb_key, &value); if (status.IsNotFound()) { - return absl::nullopt; + return MutableDocument::InvalidDocument(key); } else if (status.ok()) { return DecodeMaybeDocument(value, key); } else { @@ -142,10 +138,10 @@ absl::optional LevelDbRemoteDocumentCache::Get( } } -OptionalMaybeDocumentMap LevelDbRemoteDocumentCache::GetAll( +MutableDocumentMap LevelDbRemoteDocumentCache::GetAll( const DocumentKeySet& keys) { BackgroundQueue tasks(executor_.get()); - AsyncResults>> results; + AsyncResults> results; LevelDbRemoteDocumentKey current_key; auto it = db_->current_transaction()->NewIterator(); @@ -154,7 +150,8 @@ OptionalMaybeDocumentMap LevelDbRemoteDocumentCache::GetAll( it->Seek(LevelDbRemoteDocumentKey::Key(key)); if (!it->Valid() || !current_key.Decode(it->key()) || current_key.document_key() != key) { - results.Insert(std::make_pair(key, absl::nullopt)); + results.Insert( + std::make_pair(key, MutableDocument::InvalidDocument(key))); } else { const std::string& contents = it->value(); tasks.Execute([this, &results, &key, contents] { @@ -165,30 +162,28 @@ OptionalMaybeDocumentMap LevelDbRemoteDocumentCache::GetAll( tasks.AwaitAll(); - OptionalMaybeDocumentMap map; + MutableDocumentMap map; for (const auto& entry : results.Result()) { map = map.insert(entry.first, entry.second); } return map; } -DocumentMap LevelDbRemoteDocumentCache::GetAllExisting( +MutableDocumentMap LevelDbRemoteDocumentCache::GetAllExisting( const DocumentKeySet& keys) { - DocumentMap results; - - OptionalMaybeDocumentMap docs = LevelDbRemoteDocumentCache::GetAll(keys); + MutableDocumentMap docs = LevelDbRemoteDocumentCache::GetAll(keys); for (const auto& kv : docs) { const DocumentKey& key = kv.first; - const auto& maybe_doc = kv.second; - if (maybe_doc && maybe_doc->is_document()) { - results = results.insert(key, Document(*maybe_doc)); + auto& document = kv.second; + if (!document.is_found_document()) { + docs = docs.erase(key); } } - return results; + return docs; } -DocumentMap LevelDbRemoteDocumentCache::GetMatching( +MutableDocumentMap LevelDbRemoteDocumentCache::GetMatching( const Query& query, const SnapshotVersion& since_read_time) { HARD_ASSERT( !query.IsCollectionGroupQuery(), @@ -227,7 +222,7 @@ DocumentMap LevelDbRemoteDocumentCache::GetMatching( return LevelDbRemoteDocumentCache::GetAllExisting(remote_keys); } else { BackgroundQueue tasks(executor_.get()); - AsyncResults results; + AsyncResults results; // Documents are ordered by key, so we can use a prefix scan to narrow down // the documents we need to match the query against. @@ -253,29 +248,29 @@ DocumentMap LevelDbRemoteDocumentCache::GetMatching( const std::string& contents = it->value(); tasks.Execute([this, &results, document_key, contents] { - MaybeDocument maybe_doc = DecodeMaybeDocument(contents, document_key); - if (maybe_doc.is_document()) { - results.Insert(Document(maybe_doc)); + MutableDocument document = DecodeMaybeDocument(contents, document_key); + if (document.is_found_document()) { + results.Insert(document); } }); } tasks.AwaitAll(); - DocumentMap map; - for (const Document& doc : results.Result()) { + MutableDocumentMap map; + for (const MutableDocument& doc : results.Result()) { map = map.insert(doc.key(), doc); } return map; } } -MaybeDocument LevelDbRemoteDocumentCache::DecodeMaybeDocument( +MutableDocument LevelDbRemoteDocumentCache::DecodeMaybeDocument( absl::string_view encoded, const DocumentKey& key) { StringReader reader{encoded}; auto message = Message::TryParse(&reader); - MaybeDocument maybe_document = + MutableDocument maybe_document = serializer_->DecodeMaybeDocument(&reader, *message); if (!reader.ok()) { diff --git a/Firestore/core/src/local/leveldb_remote_document_cache.h b/Firestore/core/src/local/leveldb_remote_document_cache.h index e3e0bedb76f..980ccb843e5 100644 --- a/Firestore/core/src/local/leveldb_remote_document_cache.h +++ b/Firestore/core/src/local/leveldb_remote_document_cache.h @@ -45,15 +45,13 @@ class LevelDbRemoteDocumentCache : public RemoteDocumentCache { LocalSerializer* serializer); ~LevelDbRemoteDocumentCache(); - void Add(const model::MaybeDocument& document, + void Add(const model::MutableDocument& document, const model::SnapshotVersion& read_time) override; void Remove(const model::DocumentKey& key) override; - absl::optional Get( - const model::DocumentKey& key) override; - model::OptionalMaybeDocumentMap GetAll( - const model::DocumentKeySet& keys) override; - model::DocumentMap GetMatching( + model::MutableDocument Get(const model::DocumentKey& key) override; + model::MutableDocumentMap GetAll(const model::DocumentKeySet& keys) override; + model::MutableDocumentMap GetMatching( const core::Query& query, const model::SnapshotVersion& since_read_time) override; @@ -62,10 +60,10 @@ class LevelDbRemoteDocumentCache : public RemoteDocumentCache { * Looks up a set of entries in the cache, returning only existing entries of * Type::Document. */ - model::DocumentMap GetAllExisting(const model::DocumentKeySet& keys); + model::MutableDocumentMap GetAllExisting(const model::DocumentKeySet& keys); - model::MaybeDocument DecodeMaybeDocument(absl::string_view encoded, - const model::DocumentKey& key); + model::MutableDocument DecodeMaybeDocument(absl::string_view encoded, + const model::DocumentKey& key); // The LevelDbRemoteDocumentCache instance is owned by LevelDbPersistence. LevelDbPersistence* db_; diff --git a/Firestore/core/src/local/local_documents_view.cc b/Firestore/core/src/local/local_documents_view.cc index 1e83cfb9464..ba1f0ca01d8 100644 --- a/Firestore/core/src/local/local_documents_view.cc +++ b/Firestore/core/src/local/local_documents_view.cc @@ -16,6 +16,7 @@ #include "Firestore/core/src/local/local_documents_view.h" +#include #include #include @@ -25,9 +26,8 @@ #include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/document_key_set.h" -#include "Firestore/core/src/model/document_map.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/mutation_batch.h" -#include "Firestore/core/src/model/no_document.h" #include "Firestore/core/src/model/resource_path.h" #include "Firestore/core/src/model/snapshot_version.h" #include "Firestore/core/src/util/hard_assert.h" @@ -41,79 +41,55 @@ using model::Document; using model::DocumentKey; using model::DocumentKeySet; using model::DocumentMap; -using model::MaybeDocument; -using model::MaybeDocumentMap; +using model::MutableDocument; +using model::MutableDocumentMap; using model::Mutation; using model::MutationBatch; -using model::NoDocument; -using model::OptionalMaybeDocumentMap; using model::ResourcePath; using model::SnapshotVersion; -absl::optional LocalDocumentsView::GetDocument( - const DocumentKey& key) { +const Document LocalDocumentsView::GetDocument(const DocumentKey& key) { std::vector batches = mutation_queue_->AllMutationBatchesAffectingDocumentKey(key); return GetDocument(key, batches); } -absl::optional LocalDocumentsView::GetDocument( +Document LocalDocumentsView::GetDocument( const DocumentKey& key, const std::vector& batches) { - absl::optional document = remote_document_cache_->Get(key); + MutableDocument document = remote_document_cache_->Get(key); for (const MutationBatch& batch : batches) { - document = batch.ApplyToLocalDocument(document, key); + batch.ApplyToLocalDocument(document); } - - return document; + return Document{std::move(document)}; } -OptionalMaybeDocumentMap LocalDocumentsView::ApplyLocalMutationsToDocuments( - const OptionalMaybeDocumentMap& docs, - const std::vector& batches) { - OptionalMaybeDocumentMap results; - +DocumentMap LocalDocumentsView::ApplyLocalMutationsToDocuments( + MutableDocumentMap& docs, const std::vector& batches) { + DocumentMap results; for (const auto& kv : docs) { - const DocumentKey& key = kv.first; - absl::optional local_view = kv.second; + MutableDocument local_view = kv.second; for (const MutationBatch& batch : batches) { - local_view = batch.ApplyToLocalDocument(local_view, key); + batch.ApplyToLocalDocument(local_view); } - results = results.insert(key, local_view); + results = results.insert(kv.first, std::move(local_view)); } return results; } -MaybeDocumentMap LocalDocumentsView::GetDocuments(const DocumentKeySet& keys) { - OptionalMaybeDocumentMap docs = remote_document_cache_->GetAll(keys); - return GetLocalViewOfDocuments(docs); +DocumentMap LocalDocumentsView::GetDocuments(const DocumentKeySet& keys) { + MutableDocumentMap docs = remote_document_cache_->GetAll(keys); + return GetLocalViewOfDocuments(std::move(docs)); } -MaybeDocumentMap LocalDocumentsView::GetLocalViewOfDocuments( - const OptionalMaybeDocumentMap& base_docs) { +DocumentMap LocalDocumentsView::GetLocalViewOfDocuments( + MutableDocumentMap docs) { DocumentKeySet all_keys; - for (const auto& kv : base_docs) { + for (const auto& kv : docs) { all_keys = all_keys.insert(kv.first); } std::vector batches = mutation_queue_->AllMutationBatchesAffectingDocumentKeys(all_keys); - - OptionalMaybeDocumentMap docs = - ApplyLocalMutationsToDocuments(base_docs, batches); - - MaybeDocumentMap results; - for (const auto& kv : docs) { - const DocumentKey& key = kv.first; - absl::optional maybe_doc = kv.second; - - // TODO(http://b/32275378): Don't conflate missing / deleted. - if (!maybe_doc) { - maybe_doc = NoDocument(key, SnapshotVersion::None(), - /* has_committed_mutations= */ false); - } - results = results.insert(key, *maybe_doc); - } - - return results; + return ApplyLocalMutationsToDocuments(docs, batches); } DocumentMap LocalDocumentsView::GetDocumentsMatchingQuery( @@ -131,9 +107,9 @@ DocumentMap LocalDocumentsView::GetDocumentsMatchingDocumentQuery( const ResourcePath& doc_path) { DocumentMap result; // Just do a simple document lookup. - absl::optional doc = GetDocument(DocumentKey{doc_path}); - if (doc && doc->is_document()) { - result = result.insert(doc->key(), Document(*doc)); + Document doc = GetDocument(DocumentKey{doc_path}); + if (doc->is_found_document()) { + result = result.insert(doc->key(), doc); } return result; } @@ -156,7 +132,7 @@ model::DocumentMap LocalDocumentsView::GetDocumentsMatchingCollectionGroupQuery( query.AsCollectionQueryAtPath(parent.Append(collection_id)); DocumentMap collection_results = GetDocumentsMatchingCollectionQuery(collection_query, since_read_time); - for (const auto& kv : collection_results.underlying_map()) { + for (const auto& kv : collection_results) { const DocumentKey& key = kv.first; results = results.insert(key, Document(kv.second)); } @@ -166,13 +142,14 @@ model::DocumentMap LocalDocumentsView::GetDocumentsMatchingCollectionGroupQuery( DocumentMap LocalDocumentsView::GetDocumentsMatchingCollectionQuery( const Query& query, const SnapshotVersion& since_read_time) { - DocumentMap results = + MutableDocumentMap remote_documents = remote_document_cache_->GetMatching(query, since_read_time); // Get locally persisted mutation batches. std::vector matching_batches = mutation_queue_->AllMutationBatchesAffectingQuery(query); - results = AddMissingBaseDocuments(matching_batches, std::move(results)); + remote_documents = + AddMissingBaseDocuments(matching_batches, std::move(remote_documents)); for (const MutationBatch& batch : matching_batches) { for (const Mutation& mutation : batch.mutations()) { @@ -184,17 +161,14 @@ DocumentMap LocalDocumentsView::GetDocumentsMatchingCollectionQuery( const DocumentKey& key = mutation.key(); // base_doc may be unset for the documents that weren't yet written to // the backend. - absl::optional base_doc = - results.underlying_map().get(key); - - absl::optional mutated_doc = - mutation.ApplyToLocalView(base_doc, batch.local_write_time()); - - if (mutated_doc && mutated_doc->is_document()) { - results = results.insert(key, Document(*mutated_doc)); - } else { - results = results.erase(key); + absl::optional document = remote_documents.get(key); + if (!document) { + // Create invalid document to apply mutations on top of + document = MutableDocument::InvalidDocument(key); } + + mutation.ApplyToLocalView(*document, batch.local_write_time()); + remote_documents = remote_documents.insert(key, *document); } } @@ -202,38 +176,38 @@ DocumentMap LocalDocumentsView::GetDocumentsMatchingCollectionQuery( // that the extra reference here prevents DocumentMap's destructor from // deallocating the initial unfiltered results while we're iterating over // them. - DocumentMap unfiltered = results; - for (const auto& kv : unfiltered.underlying_map()) { + DocumentMap results; + for (const auto& kv : remote_documents) { const DocumentKey& key = kv.first; - Document doc(kv.second); - if (!query.Matches(doc)) { - results = results.erase(key); + if (query.Matches(kv.second)) { + results = results.insert(key, kv.second); } } return results; } -DocumentMap LocalDocumentsView::AddMissingBaseDocuments( +MutableDocumentMap LocalDocumentsView::AddMissingBaseDocuments( const std::vector& matching_batches, - DocumentMap existing_docs) { + MutableDocumentMap existing_docs) { DocumentKeySet missing_doc_keys; for (const MutationBatch& batch : matching_batches) { for (const Mutation& mutation : batch.mutations()) { const DocumentKey& key = mutation.key(); if (mutation.type() == Mutation::Type::Patch && - !existing_docs.underlying_map().contains(key)) { + !existing_docs.contains(key)) { missing_doc_keys = missing_doc_keys.insert(key); } } } - OptionalMaybeDocumentMap missing_docs = + MutableDocumentMap merged_docs = existing_docs; + MutableDocumentMap missing_docs = remote_document_cache_->GetAll(missing_doc_keys); for (const auto& kv : missing_docs) { - const absl::optional& maybe_doc = kv.second; - if (maybe_doc && maybe_doc->is_document()) { - existing_docs = existing_docs.insert(kv.first, Document(*maybe_doc)); + const MutableDocument document = kv.second; + if (document.is_found_document()) { + existing_docs = existing_docs.insert(kv.first, document); } } diff --git a/Firestore/core/src/local/local_documents_view.h b/Firestore/core/src/local/local_documents_view.h index 9b08a1ca04a..1bcbea88195 100644 --- a/Firestore/core/src/local/local_documents_view.h +++ b/Firestore/core/src/local/local_documents_view.h @@ -31,6 +31,10 @@ namespace core { class Query; } // namespace core +namespace model { +class Document; +} // namespace model + namespace local { /** @@ -57,8 +61,7 @@ class LocalDocumentsView { * @return Local view of the document or nil if we don't have any cached state * for it. */ - absl::optional GetDocument( - const model::DocumentKey& key); + const model::Document GetDocument(const model::DocumentKey& key); /** * Gets the local view of the documents identified by `keys`. @@ -66,14 +69,14 @@ class LocalDocumentsView { * If we don't have cached state for a document in `keys`, a DeletedDocument * will be stored for that key in the resulting set. */ - model::MaybeDocumentMap GetDocuments(const model::DocumentKeySet& keys); + model::DocumentMap GetDocuments(const model::DocumentKeySet& keys); /** * Similar to `GetDocuments`, but creates the local view from the given * `base_docs` without retrieving documents from the local store. */ - model::MaybeDocumentMap GetLocalViewOfDocuments( - const model::OptionalMaybeDocumentMap& base_docs); + model::DocumentMap GetLocalViewOfDocuments( + model::MutableDocumentMap base_docs); /** * Performs a query against the local view of all documents. @@ -90,16 +93,15 @@ class LocalDocumentsView { friend class CountingQueryEngine; // For testing /** Internal version of GetDocument that allows re-using batches. */ - absl::optional GetDocument( - const model::DocumentKey& key, - const std::vector& batches); + model::Document GetDocument(const model::DocumentKey& key, + const std::vector& batches); /** * Returns the view of the given `docs` as they would appear after applying * all mutations in the given `batches`. */ - model::OptionalMaybeDocumentMap ApplyLocalMutationsToDocuments( - const model::OptionalMaybeDocumentMap& docs, + static model::DocumentMap ApplyLocalMutationsToDocuments( + model::MutableDocumentMap& docs, const std::vector& batches); /** Performs a simple document lookup for the given path. */ @@ -122,9 +124,9 @@ class LocalDocumentsView { * `PatchMutation`s will be ignored because no base document can be found, and * lead to missing results for the query. */ - model::DocumentMap AddMissingBaseDocuments( + model::MutableDocumentMap AddMissingBaseDocuments( const std::vector& matching_batches, - model::DocumentMap existing_docs); + model::MutableDocumentMap existing_docs); RemoteDocumentCache* remote_document_cache() { return remote_document_cache_; diff --git a/Firestore/core/src/local/local_serializer.cc b/Firestore/core/src/local/local_serializer.cc index 5c0085e190a..770c5d9474b 100644 --- a/Firestore/core/src/local/local_serializer.cc +++ b/Firestore/core/src/local/local_serializer.cc @@ -31,17 +31,15 @@ #include "Firestore/core/src/bundle/named_query.h" #include "Firestore/core/src/core/query.h" #include "Firestore/core/src/local/target_data.h" -#include "Firestore/core/src/model/document.h" -#include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/mutation_batch.h" -#include "Firestore/core/src/model/no_document.h" #include "Firestore/core/src/model/snapshot_version.h" -#include "Firestore/core/src/model/unknown_document.h" #include "Firestore/core/src/nanopb/byte_string.h" #include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/nanopb/nanopb_util.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/string_format.h" +#include "absl/types/span.h" namespace firebase { namespace firestore { @@ -52,68 +50,58 @@ using bundle::BundledQuery; using bundle::BundleMetadata; using bundle::NamedQuery; using core::Target; -using model::Document; -using model::DocumentState; -using model::MaybeDocument; +using model::DeepClone; +using model::FieldTransform; +using model::MutableDocument; using model::Mutation; using model::MutationBatch; -using model::NoDocument; using model::ObjectValue; using model::SnapshotVersion; -using model::UnknownDocument; using nanopb::ByteString; using nanopb::CheckedSize; using nanopb::CopyBytesArray; using nanopb::MakeArray; using nanopb::Message; using nanopb::Reader; +using nanopb::ReleaseFieldOwnership; using nanopb::SafeReadBoolean; +using nanopb::SetRepeatedField; +using nanopb::Writer; +using util::Status; using util::StringFormat; } // namespace Message LocalSerializer::EncodeMaybeDocument( - const MaybeDocument& maybe_doc) const { + const MutableDocument& document) const { Message result; - switch (maybe_doc.type()) { - case MaybeDocument::Type::Document: { - result->which_document_type = firestore_client_MaybeDocument_document_tag; - Document doc(maybe_doc); - // TODO(b/142956770): other platforms check for whether the `Document` - // contains a memoized proto and use it if available instead of - // re-encoding. - result->document = EncodeDocument(doc); - result->has_committed_mutations = doc.has_committed_mutations(); - return result; - } - - case MaybeDocument::Type::NoDocument: { - result->which_document_type = - firestore_client_MaybeDocument_no_document_tag; - NoDocument no_doc(maybe_doc); - result->no_document = EncodeNoDocument(no_doc); - result->has_committed_mutations = no_doc.has_committed_mutations(); - return result; - } - - case MaybeDocument::Type::UnknownDocument: - result->which_document_type = - firestore_client_MaybeDocument_unknown_document_tag; - result->unknown_document = - EncodeUnknownDocument(UnknownDocument(maybe_doc)); - result->has_committed_mutations = true; - return result; - - case MaybeDocument::Type::Invalid: - HARD_FAIL("Unknown document type %s", maybe_doc.type()); + if (document.is_found_document()) { + result->which_document_type = firestore_client_MaybeDocument_document_tag; + result->document = EncodeDocument(document); + result->has_committed_mutations = document.has_committed_mutations(); + return result; + } else if (document.is_no_document()) { + result->which_document_type = + firestore_client_MaybeDocument_no_document_tag; + result->no_document = EncodeNoDocument(document); + result->has_committed_mutations = document.has_committed_mutations(); + return result; + } else if (document.is_unknown_document()) { + result->which_document_type = + firestore_client_MaybeDocument_unknown_document_tag; + result->unknown_document = EncodeUnknownDocument(document); + result->has_committed_mutations = true; + return result; + } else { + HARD_FAIL("Unknown document type %s", document.ToString()); } UNREACHABLE(); } -MaybeDocument LocalSerializer::DecodeMaybeDocument( - Reader* reader, const firestore_client_MaybeDocument& proto) const { +MutableDocument LocalSerializer::DecodeMaybeDocument( + Reader* reader, firestore_client_MaybeDocument& proto) const { if (!reader->status().ok()) return {}; switch (proto.which_document_type) { @@ -130,8 +118,8 @@ MaybeDocument LocalSerializer::DecodeMaybeDocument( default: reader->Fail( - StringFormat("Invalid MaybeDocument document type: %s. Expected " - "'no_document' (%s) or 'document' (%s)", + StringFormat("Invalid document type: %s. Expected 'no_document' (%s) " + "or 'document' (%s)", proto.which_document_type, firestore_client_MaybeDocument_no_document_tag, firestore_client_MaybeDocument_document_tag)); @@ -142,21 +130,23 @@ MaybeDocument LocalSerializer::DecodeMaybeDocument( } google_firestore_v1_Document LocalSerializer::EncodeDocument( - const Document& doc) const { + const MutableDocument& doc) const { google_firestore_v1_Document result{}; result.name = rpc_serializer_.EncodeKey(doc.key()); // Encode Document.fields (unless it's empty) - pb_size_t count = CheckedSize(doc.data().GetInternalValue().size()); - result.fields_count = count; - result.fields = MakeArray(count); - int i = 0; - for (const auto& kv : doc.data().GetInternalValue()) { - result.fields[i].key = rpc_serializer_.EncodeString(kv.first); - result.fields[i].value = rpc_serializer_.EncodeFieldValue(kv.second); - i++; - } + google_firestore_v1_MapValue fields_map = doc.value().map_value; + SetRepeatedField( + &result.fields, &result.fields_count, + absl::Span( + fields_map.fields, fields_map.fields_count), + [](const google_firestore_v1_MapValue_FieldsEntry& map_entry) { + // TODO(mrschmidt): Figure out how to remove this copy + return google_firestore_v1_Document_FieldsEntry{ + nanopb::CopyBytesArray(map_entry.key), + *DeepClone(map_entry.value).release()}; + }); result.has_update_time = true; result.update_time = rpc_serializer_.EncodeVersion(doc.version()); @@ -165,25 +155,26 @@ google_firestore_v1_Document LocalSerializer::EncodeDocument( return result; } -Document LocalSerializer::DecodeDocument( +MutableDocument LocalSerializer::DecodeDocument( Reader* reader, - const google_firestore_v1_Document& proto, + google_firestore_v1_Document& proto, bool has_committed_mutations) const { - ObjectValue fields = rpc_serializer_.DecodeFields( - reader->context(), proto.fields_count, proto.fields); + ObjectValue fields = + ObjectValue::FromFieldsEntry(proto.fields, proto.fields_count); SnapshotVersion version = rpc_serializer_.DecodeVersion(reader->context(), proto.update_time); - DocumentState state = has_committed_mutations - ? DocumentState::kCommittedMutations - : DocumentState::kSynced; - return Document(std::move(fields), - rpc_serializer_.DecodeKey(reader->context(), proto.name), - version, state); + MutableDocument document = MutableDocument::FoundDocument( + rpc_serializer_.DecodeKey(reader->context(), proto.name), version, + std::move(fields)); + if (has_committed_mutations) { + document.SetHasCommittedMutations(); + } + return document; } firestore_client_NoDocument LocalSerializer::EncodeNoDocument( - const NoDocument& no_doc) const { + const MutableDocument& no_doc) const { firestore_client_NoDocument result{}; result.name = rpc_serializer_.EncodeKey(no_doc.key()); @@ -192,19 +183,23 @@ firestore_client_NoDocument LocalSerializer::EncodeNoDocument( return result; } -NoDocument LocalSerializer::DecodeNoDocument( +MutableDocument LocalSerializer::DecodeNoDocument( Reader* reader, const firestore_client_NoDocument& proto, bool has_committed_mutations) const { SnapshotVersion version = rpc_serializer_.DecodeVersion(reader->context(), proto.read_time); - return NoDocument(rpc_serializer_.DecodeKey(reader->context(), proto.name), - version, has_committed_mutations); + MutableDocument document = MutableDocument::NoDocument( + rpc_serializer_.DecodeKey(reader->context(), proto.name), version); + if (has_committed_mutations) { + document.SetHasCommittedMutations(); + } + return document; } firestore_client_UnknownDocument LocalSerializer::EncodeUnknownDocument( - const UnknownDocument& unknown_doc) const { + const MutableDocument& unknown_doc) const { firestore_client_UnknownDocument result{}; result.name = rpc_serializer_.EncodeKey(unknown_doc.key()); @@ -213,12 +208,12 @@ firestore_client_UnknownDocument LocalSerializer::EncodeUnknownDocument( return result; } -UnknownDocument LocalSerializer::DecodeUnknownDocument( +MutableDocument LocalSerializer::DecodeUnknownDocument( Reader* reader, const firestore_client_UnknownDocument& proto) const { SnapshotVersion version = rpc_serializer_.DecodeVersion(reader->context(), proto.version); - return UnknownDocument( + return MutableDocument::UnknownDocument( rpc_serializer_.DecodeKey(reader->context(), proto.name), version); } @@ -254,7 +249,7 @@ Message LocalSerializer::EncodeTargetData( } TargetData LocalSerializer::DecodeTargetData( - Reader* reader, const firestore_client_Target& proto) const { + Reader* reader, firestore_client_Target& proto) const { if (!reader->status().ok()) return TargetData(); model::TargetId target_id = proto.target_id; @@ -303,7 +298,7 @@ Message LocalSerializer::EncodeMutationBatch( int i = 0; for (const auto& mutation : mutation_batch.base_mutations()) { result->base_writes[i] = rpc_serializer_.EncodeMutation(mutation); - i++; + ++i; } count = CheckedSize(mutation_batch.mutations().size()); @@ -312,7 +307,7 @@ Message LocalSerializer::EncodeMutationBatch( i = 0; for (const auto& mutation : mutation_batch.mutations()) { result->writes[i] = rpc_serializer_.EncodeMutation(mutation); - i++; + ++i; } result->local_write_time = @@ -322,7 +317,7 @@ Message LocalSerializer::EncodeMutationBatch( } MutationBatch LocalSerializer::DecodeMutationBatch( - nanopb::Reader* reader, const firestore_client_WriteBatch& proto) const { + nanopb::Reader* reader, firestore_client_WriteBatch& proto) const { int batch_id = proto.batch_id; Timestamp local_write_time = rpc_serializer_.DecodeTimestamp( reader->context(), proto.local_write_time); @@ -341,22 +336,25 @@ MutationBatch LocalSerializer::DecodeMutationBatch( // updated to `update_transforms`. // TODO(b/174608374): Remove this code once we perform a schema migration. for (size_t i = 0; i < proto.writes_count; ++i) { - _google_firestore_v1_Write current_mutation = proto.writes[i]; + google_firestore_v1_Write current_mutation = proto.writes[i]; bool has_transform = i + 1 < proto.writes_count && proto.writes[i + 1].which_operation == google_firestore_v1_Write_transform_tag; if (has_transform) { - _google_firestore_v1_Write transform_mutation = proto.writes[i + 1]; + google_firestore_v1_Write& transform_mutation = proto.writes[i + 1]; HARD_ASSERT( proto.writes[i].which_operation == google_firestore_v1_Write_update_tag, "TransformMutation should be preceded by a patch or set mutation"); - _google_firestore_v1_Write new_mutation{current_mutation}; + google_firestore_v1_Write new_mutation{current_mutation}; new_mutation.update_transforms_count = transform_mutation.transform.field_transforms_count; new_mutation.update_transforms = transform_mutation.transform.field_transforms; - + // Prevent double-freeing of the write's fields. The fields are now owned + // by the mutation. + transform_mutation.transform.field_transforms_count = 0; + transform_mutation.transform.field_transforms = nullptr; mutations.push_back( rpc_serializer_.DecodeMutation(reader->context(), new_mutation)); ++i; @@ -411,7 +409,7 @@ Message LocalSerializer::EncodeNamedQuery( } NamedQuery LocalSerializer::DecodeNamedQuery( - nanopb::Reader* reader, const firestore_NamedQuery& proto) const { + nanopb::Reader* reader, firestore_NamedQuery& proto) const { return NamedQuery(rpc_serializer_.DecodeString(proto.name), DecodeBundledQuery(reader, proto.bundled_query), DecodeVersion(reader, proto.read_time)); @@ -428,7 +426,7 @@ firestore_BundledQuery LocalSerializer::EncodeBundledQuery( firestore_BundledQuery_LimitType_LAST; auto query_target = rpc_serializer_.EncodeQueryTarget(query.target()); - result.parent = CopyBytesArray(query_target.parent); + result.parent = query_target.parent; result.which_query_type = firestore_BundledQuery_structured_query_tag; result.structured_query = query_target.structured_query; @@ -436,7 +434,7 @@ firestore_BundledQuery LocalSerializer::EncodeBundledQuery( } BundledQuery LocalSerializer::DecodeBundledQuery( - nanopb::Reader* reader, const firestore_BundledQuery& query) const { + nanopb::Reader* reader, firestore_BundledQuery& query) const { // The QueryTarget oneof only has a single valid value. if (query.which_query_type != firestore_BundledQuery_structured_query_tag) { reader->Fail( diff --git a/Firestore/core/src/local/local_serializer.h b/Firestore/core/src/local/local_serializer.h index 671198644ee..2a7396ec685 100644 --- a/Firestore/core/src/local/local_serializer.h +++ b/Firestore/core/src/local/local_serializer.h @@ -87,15 +87,15 @@ class LocalSerializer { * local storage. */ nanopb::Message EncodeMaybeDocument( - const model::MaybeDocument& maybe_doc) const; + const model::MutableDocument& maybe_doc) const; /** * @brief Decodes nanopb proto representing a MaybeDocument proto to the * equivalent model. + * Modifies the provided proto to release ownership of any Value messages. */ - model::MaybeDocument DecodeMaybeDocument( - nanopb::Reader* reader, - const firestore_client_MaybeDocument& proto) const; + model::MutableDocument DecodeMaybeDocument( + nanopb::Reader* reader, firestore_client_MaybeDocument& proto) const; /** * @brief Encodes a TargetData to the equivalent nanopb proto, representing a @@ -107,9 +107,10 @@ class LocalSerializer { /** * @brief Decodes nanopb proto representing a ::firestore::proto::Target proto * to the equivalent TargetData. + * Modifies the provided proto to release ownership of any Value messages. */ TargetData DecodeTargetData(nanopb::Reader* reader, - const firestore_client_Target& proto) const; + firestore_client_Target& proto) const; /** * @brief Encodes a MutationBatch to the equivalent nanopb proto, representing @@ -121,9 +122,10 @@ class LocalSerializer { /** * @brief Decodes a nanopb proto representing a * ::firestore::client::WriteBatch proto to the equivalent MutationBatch. + * Modifies the provided proto to release ownership of any Value messages. */ model::MutationBatch DecodeMutationBatch( - nanopb::Reader* reader, const firestore_client_WriteBatch& proto) const; + nanopb::Reader* reader, firestore_client_WriteBatch& proto) const; google_protobuf_Timestamp EncodeVersion( const model::SnapshotVersion& version) const; @@ -138,8 +140,13 @@ class LocalSerializer { nanopb::Message EncodeNamedQuery( const bundle::NamedQuery& query) const; + + /** + * Decodes the named query. Modifies the provided proto to release ownership + * of any Value messages. + */ bundle::NamedQuery DecodeNamedQuery(nanopb::Reader* reader, - const firestore_NamedQuery& proto) const; + firestore_NamedQuery& proto) const; private: /** @@ -147,29 +154,31 @@ class LocalSerializer { * serializer for Documents in that it preserves the update_time, which is * considered an output only value by the server. */ - google_firestore_v1_Document EncodeDocument(const model::Document& doc) const; + google_firestore_v1_Document EncodeDocument( + const model::MutableDocument& doc) const; - model::Document DecodeDocument(nanopb::Reader* reader, - const google_firestore_v1_Document& proto, - bool has_committed_mutations) const; + model::MutableDocument DecodeDocument(nanopb::Reader* reader, + google_firestore_v1_Document& proto, + bool has_committed_mutations) const; firestore_client_NoDocument EncodeNoDocument( - const model::NoDocument& no_doc) const; + const model::MutableDocument& no_doc) const; - model::NoDocument DecodeNoDocument(nanopb::Reader* reader, - const firestore_client_NoDocument& proto, - bool has_committed_mutations) const; + model::MutableDocument DecodeNoDocument( + nanopb::Reader* reader, + const firestore_client_NoDocument& proto, + bool has_committed_mutations) const; firestore_client_UnknownDocument EncodeUnknownDocument( - const model::UnknownDocument& unknown_doc) const; - model::UnknownDocument DecodeUnknownDocument( + const model::MutableDocument& unknown_doc) const; + model::MutableDocument DecodeUnknownDocument( nanopb::Reader* reader, const firestore_client_UnknownDocument& proto) const; firestore_BundledQuery EncodeBundledQuery( const bundle::BundledQuery& query) const; - bundle::BundledQuery DecodeBundledQuery( - nanopb::Reader* reader, const firestore_BundledQuery& query) const; + bundle::BundledQuery DecodeBundledQuery(nanopb::Reader* reader, + firestore_BundledQuery& query) const; remote::Serializer rpc_serializer_; }; diff --git a/Firestore/core/src/local/local_store.cc b/Firestore/core/src/local/local_store.cc index 61a5ac9aaf8..6d2ac79a591 100644 --- a/Firestore/core/src/local/local_store.cc +++ b/Firestore/core/src/local/local_store.cc @@ -29,6 +29,7 @@ #include "Firestore/core/src/local/query_result.h" #include "Firestore/core/src/local/reference_delegate.h" #include "Firestore/core/src/local/target_cache.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/mutation_batch.h" #include "Firestore/core/src/model/mutation_batch_result.h" #include "Firestore/core/src/model/patch_mutation.h" @@ -46,19 +47,19 @@ using core::Query; using core::Target; using core::TargetIdGenerator; using model::BatchId; +using model::Document; using model::DocumentKey; using model::DocumentKeySet; using model::DocumentMap; using model::DocumentUpdateMap; using model::DocumentVersionMap; using model::ListenSequenceNumber; -using model::MaybeDocument; -using model::MaybeDocumentMap; +using model::MutableDocument; +using model::MutableDocumentMap; using model::Mutation; using model::MutationBatch; using model::MutationBatchResult; using model::ObjectValue; -using model::OptionalMaybeDocumentMap; using model::PatchMutation; using model::Precondition; using model::ResourcePath; @@ -108,7 +109,7 @@ void LocalStore::StartMutationQueue() { persistence_->Run("Start MutationQueue", [&] { mutation_queue_->Start(); }); } -MaybeDocumentMap LocalStore::HandleUserChange(const User& user) { +DocumentMap LocalStore::HandleUserChange(const User& user) { // Swap out the mutation queue, grabbing the pending mutation batches before // and after. std::vector old_batches = persistence_->Run( @@ -157,7 +158,7 @@ LocalWriteResult LocalStore::WriteLocally(std::vector&& mutations) { // Load and apply all existing mutations. This lets us compute the current // base state for all non-idempotent transforms before applying any // additional user-provided writes. - MaybeDocumentMap existing_documents = local_documents_->GetDocuments(keys); + DocumentMap existing_documents = local_documents_->GetDocuments(keys); // For non-idempotent mutations (such as `FieldValue.increment()`), we // record the base state in a separate patch mutation. This is later used to @@ -165,29 +166,29 @@ LocalWriteResult LocalStore::WriteLocally(std::vector&& mutations) { // sends us an update that already includes our transform. std::vector base_mutations; for (const Mutation& mutation : mutations) { - absl::optional base_document = + absl::optional base_document = existing_documents.get(mutation.key()); absl::optional base_value = - mutation.ExtractTransformBaseValue(base_document); + mutation.ExtractTransformBaseValue(*base_document); if (base_value) { // NOTE: The base state should only be applied if there's some existing // document to override, so use a Precondition of exists=true - base_mutations.push_back(PatchMutation(mutation.key(), *base_value, - base_value->ToFieldMask(), + model::FieldMask mask = base_value->ToFieldMask(); + base_mutations.push_back(PatchMutation(mutation.key(), + std::move(*base_value), mask, Precondition::Exists(true))); } } MutationBatch batch = mutation_queue_->AddMutationBatch( local_write_time, std::move(base_mutations), std::move(mutations)); - MaybeDocumentMap changed_documents = - batch.ApplyToLocalDocumentSet(existing_documents); - return LocalWriteResult{batch.batch_id(), std::move(changed_documents)}; + batch.ApplyToLocalDocumentSet(existing_documents); + return LocalWriteResult{batch.batch_id(), std::move(existing_documents)}; }); } -MaybeDocumentMap LocalStore::AcknowledgeBatch( +DocumentMap LocalStore::AcknowledgeBatch( const MutationBatchResult& batch_result) { return persistence_->Run("Acknowledge batch", [&] { const MutationBatch& batch = batch_result.batch(); @@ -205,24 +206,17 @@ void LocalStore::ApplyBatchResult(const MutationBatchResult& batch_result) { const DocumentVersionMap& versions = batch_result.doc_versions(); for (const DocumentKey& doc_key : doc_keys) { - absl::optional remote_doc = - remote_document_cache_->Get(doc_key); - absl::optional doc = remote_doc; + MutableDocument doc = remote_document_cache_->Get(doc_key); auto ack_version_iter = versions.find(doc_key); HARD_ASSERT(ack_version_iter != versions.end(), "doc_versions should contain every doc in the write."); const SnapshotVersion& ack_version = ack_version_iter->second; - if (!doc || doc->version() < ack_version) { - doc = batch.ApplyToRemoteDocument(doc, doc_key, batch_result); - if (!doc) { - HARD_ASSERT( - !remote_doc, - "Mutation batch %s applied to document %s resulted in nullopt.", - batch.ToString(), util::ToString(remote_doc)); - } else { - remote_document_cache_->Add(*doc, batch_result.commit_version()); + if (doc.version() < ack_version) { + batch.ApplyToRemoteDocument(doc, batch_result); + if (doc.is_valid_document()) { + remote_document_cache_->Add(doc, batch_result.commit_version()); } } } @@ -230,7 +224,7 @@ void LocalStore::ApplyBatchResult(const MutationBatchResult& batch_result) { mutation_queue_->RemoveMutationBatch(batch); } -MaybeDocumentMap LocalStore::RejectBatch(BatchId batch_id) { +DocumentMap LocalStore::RejectBatch(BatchId batch_id) { return persistence_->Run("Reject batch", [&] { absl::optional to_reject = mutation_queue_->LookupMutationBatch(batch_id); @@ -256,7 +250,7 @@ const SnapshotVersion& LocalStore::GetLastRemoteSnapshotVersion() const { return target_cache_->GetLastRemoteSnapshotVersion(); } -model::MaybeDocumentMap LocalStore::ApplyRemoteEvent( +model::DocumentMap LocalStore::ApplyRemoteEvent( const remote::RemoteEvent& remote_event) { const SnapshotVersion& last_remote_version = target_cache_->GetLastRemoteSnapshotVersion(); @@ -285,8 +279,8 @@ model::MaybeDocumentMap LocalStore::ApplyRemoteEvent( // 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 reviously - // _removed from this target. + // being removed now are ordered later than documents that were previously + // removed from this target. const ByteString& resume_token = change.resume_token(); // Update the resume token if the change includes one. if (!resume_token.empty()) { @@ -414,7 +408,7 @@ absl::optional LocalStore::GetNextMutationBatch( }); } -absl::optional LocalStore::ReadDocument(const DocumentKey& key) { +const Document LocalStore::ReadDocument(const DocumentKey& key) { return persistence_->Run("ReadDocument", [&] { return local_documents_->GetDocument(key); }); } @@ -522,8 +516,8 @@ void LocalStore::SaveBundle(const bundle::BundleMetadata& metadata) { "Save bundle", [&] { bundle_cache_->SaveBundleMetadata(metadata); }); } -MaybeDocumentMap LocalStore::ApplyBundledDocuments( - const MaybeDocumentMap& bundled_documents, const std::string& bundle_id) { +DocumentMap LocalStore::ApplyBundledDocuments( + const MutableDocumentMap& bundled_documents, const std::string& bundle_id) { // Allocates a target to hold all document keys from the bundle, such that // they will not get garbage collected right away. TargetData umbrella_target = AllocateTarget(NewUmbrellaTarget(bundle_id)); @@ -535,7 +529,7 @@ MaybeDocumentMap LocalStore::ApplyBundledDocuments( for (const auto& kv : bundled_documents) { const DocumentKey& key = kv.first; const auto& doc = kv.second; - if (doc.type() == MaybeDocument::Type::Document) { + if (doc.is_found_document()) { keys = keys.insert(key); } document_updates.emplace(key, doc); @@ -591,11 +585,11 @@ Target LocalStore::NewUmbrellaTarget(const std::string& bundle_id) { .ToTarget(); } -OptionalMaybeDocumentMap LocalStore::PopulateDocumentChanges( +MutableDocumentMap LocalStore::PopulateDocumentChanges( const DocumentUpdateMap& documents, const DocumentVersionMap& document_versions, const SnapshotVersion& global_version) { - OptionalMaybeDocumentMap changed_docs; + MutableDocumentMap changed_docs; DocumentKeySet updated_keys; for (const auto& kv : documents) { @@ -603,17 +597,13 @@ OptionalMaybeDocumentMap LocalStore::PopulateDocumentChanges( } // Each loop iteration only affects its "own" doc, so it's safe to get all // the remote documents in advance in a single call. - OptionalMaybeDocumentMap existing_docs = + MutableDocumentMap existing_docs = remote_document_cache_->GetAll(updated_keys); for (const auto& kv : documents) { const DocumentKey& key = kv.first; - const MaybeDocument& doc = kv.second; - absl::optional existing_doc; - auto found_existing = existing_docs.get(key); - if (found_existing) { - existing_doc = *found_existing; - } + const MutableDocument& doc = kv.second; + MutableDocument existing_doc = *existing_docs.get(key); auto search_version = document_versions.find(key); const SnapshotVersion& read_time = search_version != document_versions.end() ? search_version->second @@ -622,15 +612,15 @@ OptionalMaybeDocumentMap LocalStore::PopulateDocumentChanges( // Note: The order of the steps below is important, since we want to // ensure that rejected limbo resolutions (which fabricate NoDocuments // with SnapshotVersion::None) never add documents to cache. - if (doc.type() == MaybeDocument::Type::NoDocument && - doc.version() == SnapshotVersion::None()) { + if (doc.is_no_document() && doc.version() == SnapshotVersion::None()) { // NoDocuments with SnapshotVersion::None are used in manufactured // events. We remove these documents from cache since we lost access. remote_document_cache_->Remove(key); changed_docs = changed_docs.insert(key, doc); - } else if (!existing_doc || doc.version() > existing_doc->version() || - (doc.version() == existing_doc->version() && - existing_doc->has_pending_writes())) { + } else if (!existing_doc.is_valid_document() || + doc.version() > existing_doc.version() || + (doc.version() == existing_doc.version() && + existing_doc.has_pending_writes())) { HARD_ASSERT(read_time != SnapshotVersion::None(), "Cannot add a document when the remote version is zero"); remote_document_cache_->Add(doc, read_time); @@ -639,7 +629,7 @@ OptionalMaybeDocumentMap LocalStore::PopulateDocumentChanges( LOG_DEBUG( "LocalStore Ignoring outdated update for %s. " "Current version: %s Remote version: %s", - key.ToString(), existing_doc->version().ToString(), + key.ToString(), existing_doc.version().ToString(), doc.version().ToString()); } } diff --git a/Firestore/core/src/local/local_store.h b/Firestore/core/src/local/local_store.h index 02b57f7cdfe..1d403e51293 100644 --- a/Firestore/core/src/local/local_store.h +++ b/Firestore/core/src/local/local_store.h @@ -28,6 +28,7 @@ #include "Firestore/core/src/core/target_id_generator.h" #include "Firestore/core/src/local/reference_set.h" #include "Firestore/core/src/local/target_data.h" +#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/model_fwd.h" #include "absl/types/optional.h" @@ -120,17 +121,16 @@ class LocalStore : public bundle::BundleCallback { * In response the local store switches the mutation queue to the new user and * returns any resulting document changes. */ - model::MaybeDocumentMap HandleUserChange(const auth::User& user); + model::DocumentMap HandleUserChange(const auth::User& user); /** Accepts locally generated Mutations and commits them to storage. */ LocalWriteResult WriteLocally(std::vector&& mutations); /** - * Returns the current value of a document with a given key, or `nullopt` if - * not found. + * Returns the current value of a document with a given key, or an invalid + * document if not found. */ - absl::optional ReadDocument( - const model::DocumentKey& key); + const model::Document ReadDocument(const model::DocumentKey& key); /** * Acknowledges the given batch. @@ -146,7 +146,7 @@ class LocalStore : public bundle::BundleCallback { * * @return The resulting (modified) documents. */ - model::MaybeDocumentMap AcknowledgeBatch( + model::DocumentMap AcknowledgeBatch( const model::MutationBatchResult& batch_result); /** @@ -155,7 +155,7 @@ class LocalStore : public bundle::BundleCallback { * * @return The resulting (modified) documents. */ - model::MaybeDocumentMap RejectBatch(model::BatchId batch_id); + model::DocumentMap RejectBatch(model::BatchId batch_id); /** Returns the last recorded stream token for the current user. */ nanopb::ByteString GetLastStreamToken(); @@ -181,8 +181,7 @@ class LocalStore : public bundle::BundleCallback { * LocalDocuments are re-calculated if there are remaining mutations in the * queue. */ - model::MaybeDocumentMap ApplyRemoteEvent( - const remote::RemoteEvent& remote_event); + model::DocumentMap ApplyRemoteEvent(const remote::RemoteEvent& remote_event); /** * Returns the keys of the documents that are associated with the given @@ -259,8 +258,8 @@ class LocalStore : public bundle::BundleCallback { * Local documents are re-calculated if there are remaining mutations in the * queue. */ - model::MaybeDocumentMap ApplyBundledDocuments( - const model::MaybeDocumentMap& documents, + model::DocumentMap ApplyBundledDocuments( + const model::MutableDocumentMap& documents, const std::string& bundle_id) override; /** Saves the given `NamedQuery` to local persistence. */ @@ -322,7 +321,7 @@ class LocalStore : public bundle::BundleCallback { * @param global_version A SnapshotVersion representing the read time if all * documents have the same read time. */ - model::OptionalMaybeDocumentMap PopulateDocumentChanges( + model::MutableDocumentMap PopulateDocumentChanges( const model::DocumentUpdateMap& documents, const model::DocumentVersionMap& document_versions, const model::SnapshotVersion& global_version); diff --git a/Firestore/core/src/local/local_view_changes.cc b/Firestore/core/src/local/local_view_changes.cc index 4b56760f624..39051b485af 100644 --- a/Firestore/core/src/local/local_view_changes.cc +++ b/Firestore/core/src/local/local_view_changes.cc @@ -35,11 +35,11 @@ LocalViewChanges LocalViewChanges::FromViewSnapshot( for (const DocumentViewChange& doc_change : snapshot.document_changes()) { switch (doc_change.type()) { case DocumentViewChange::Type::Added: - added_keys = added_keys.insert(doc_change.document().key()); + added_keys = added_keys.insert(doc_change.document()->key()); break; case DocumentViewChange::Type::Removed: - removed_keys = removed_keys.insert(doc_change.document().key()); + removed_keys = removed_keys.insert(doc_change.document()->key()); break; default: diff --git a/Firestore/core/src/local/local_write_result.h b/Firestore/core/src/local/local_write_result.h index 7977c69c38c..0adf51012ad 100644 --- a/Firestore/core/src/local/local_write_result.h +++ b/Firestore/core/src/local/local_write_result.h @@ -19,7 +19,7 @@ #include -#include "Firestore/core/src/model/document_map.h" +#include "Firestore/core/src/model/model_fwd.h" #include "Firestore/core/src/model/types.h" namespace firebase { @@ -29,7 +29,7 @@ namespace local { /** The result of a write to the local store. */ class LocalWriteResult { public: - LocalWriteResult(model::BatchId batch_id, model::MaybeDocumentMap&& changes) + LocalWriteResult(model::BatchId batch_id, model::DocumentMap&& changes) : batch_id_(batch_id), changes_(std::move(changes)) { } @@ -41,13 +41,13 @@ class LocalWriteResult { } /** The document changes resulting from the local write. */ - const model::MaybeDocumentMap& changes() const { + const model::DocumentMap& changes() const { return changes_; } private: model::BatchId batch_id_; - model::MaybeDocumentMap changes_; + model::DocumentMap changes_; }; } // namespace local diff --git a/Firestore/core/src/local/memory_remote_document_cache.cc b/Firestore/core/src/local/memory_remote_document_cache.cc index 544dd8947d6..2df5d473ffc 100644 --- a/Firestore/core/src/local/memory_remote_document_cache.cc +++ b/Firestore/core/src/local/memory_remote_document_cache.cc @@ -20,7 +20,7 @@ #include "Firestore/core/src/local/memory_lru_reference_delegate.h" #include "Firestore/core/src/local/memory_persistence.h" #include "Firestore/core/src/local/sizer.h" -#include "Firestore/core/src/model/document_map.h" +#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/util/hard_assert.h" namespace firebase { @@ -31,11 +31,9 @@ using core::Query; using model::Document; using model::DocumentKey; using model::DocumentKeySet; -using model::DocumentMap; using model::ListenSequenceNumber; -using model::MaybeDocument; -using model::MaybeDocumentMap; -using model::OptionalMaybeDocumentMap; +using model::MutableDocument; +using model::MutableDocumentMap; using model::SnapshotVersion; MemoryRemoteDocumentCache::MemoryRemoteDocumentCache( @@ -43,8 +41,9 @@ MemoryRemoteDocumentCache::MemoryRemoteDocumentCache( persistence_ = persistence; } -void MemoryRemoteDocumentCache::Add(const MaybeDocument& document, +void MemoryRemoteDocumentCache::Add(const MutableDocument& document, const model::SnapshotVersion& read_time) { + // Note: We create an explicit copy to prevent further modifications. docs_ = docs_.insert(document.key(), std::make_pair(document, read_time)); persistence_->index_manager()->AddToCollectionParentIndex( @@ -55,15 +54,16 @@ void MemoryRemoteDocumentCache::Remove(const DocumentKey& key) { docs_ = docs_.erase(key); } -absl::optional MemoryRemoteDocumentCache::Get( - const DocumentKey& key) { +MutableDocument MemoryRemoteDocumentCache::Get(const DocumentKey& key) { const auto& entry = docs_.get(key); - return entry ? entry->first : absl::optional(); + // Note: We create an explicit copy to prevent modifications of the backing + // data. + return entry ? entry->first.Clone() : MutableDocument::InvalidDocument(key); } -OptionalMaybeDocumentMap MemoryRemoteDocumentCache::GetAll( +MutableDocumentMap MemoryRemoteDocumentCache::GetAll( const DocumentKeySet& keys) { - OptionalMaybeDocumentMap results; + MutableDocumentMap results; for (const DocumentKey& key : keys) { // Make sure each key has a corresponding entry, which is nullopt in case // the document is not found. @@ -73,13 +73,13 @@ OptionalMaybeDocumentMap MemoryRemoteDocumentCache::GetAll( return results; } -DocumentMap MemoryRemoteDocumentCache::GetMatching( +MutableDocumentMap MemoryRemoteDocumentCache::GetMatching( const Query& query, const SnapshotVersion& since_read_time) { HARD_ASSERT( !query.IsCollectionGroupQuery(), "CollectionGroup queries should be handled in LocalDocumentsView"); - DocumentMap results; + MutableDocumentMap results; // Documents are ordered by key, so we can use a prefix scan to narrow down // the documents we need to match the query against. @@ -89,8 +89,8 @@ DocumentMap MemoryRemoteDocumentCache::GetMatching( if (!query.path().IsPrefixOf(key.path())) { break; } - const MaybeDocument& maybe_doc = it->second.first; - if (!maybe_doc.is_document()) { + const MutableDocument& document = it->second.first; + if (!document.is_found_document()) { continue; } @@ -99,10 +99,13 @@ DocumentMap MemoryRemoteDocumentCache::GetMatching( continue; } - Document doc(maybe_doc); - if (query.Matches(doc)) { - results = results.insert(key, std::move(doc)); + if (!query.Matches(document)) { + continue; } + + // Note: We create an explicit copy to prevent modifications or the backing + // data. + results = results.insert(key, document.Clone()); } return results; } @@ -126,8 +129,8 @@ std::vector MemoryRemoteDocumentCache::RemoveOrphanedDocuments( int64_t MemoryRemoteDocumentCache::CalculateByteSize(const Sizer& sizer) { int64_t count = 0; for (const auto& kv : docs_) { - const MaybeDocument& maybe_doc = kv.second.first; - count += sizer.CalculateByteSize(maybe_doc); + const MutableDocument& document = kv.second.first; + count += sizer.CalculateByteSize(document); } return count; } diff --git a/Firestore/core/src/local/memory_remote_document_cache.h b/Firestore/core/src/local/memory_remote_document_cache.h index a6d0e67f11b..15cdb9c1dee 100644 --- a/Firestore/core/src/local/memory_remote_document_cache.h +++ b/Firestore/core/src/local/memory_remote_document_cache.h @@ -23,8 +23,8 @@ #include "Firestore/core/src/immutable/sorted_map.h" #include "Firestore/core/src/local/remote_document_cache.h" #include "Firestore/core/src/model/document_key.h" -#include "Firestore/core/src/model/maybe_document.h" #include "Firestore/core/src/model/model_fwd.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/types.h" namespace firebase { @@ -39,15 +39,13 @@ class MemoryRemoteDocumentCache : public RemoteDocumentCache { public: explicit MemoryRemoteDocumentCache(MemoryPersistence* persistence); - void Add(const model::MaybeDocument& document, + void Add(const model::MutableDocument& document, const model::SnapshotVersion& read_time) override; void Remove(const model::DocumentKey& key) override; - absl::optional Get( - const model::DocumentKey& key) override; - model::OptionalMaybeDocumentMap GetAll( - const model::DocumentKeySet& keys) override; - model::DocumentMap GetMatching( + model::MutableDocument Get(const model::DocumentKey& key) override; + model::MutableDocumentMap GetAll(const model::DocumentKeySet& keys) override; + model::MutableDocumentMap GetMatching( const core::Query& query, const model::SnapshotVersion& since_read_time) override; @@ -59,8 +57,9 @@ class MemoryRemoteDocumentCache : public RemoteDocumentCache { private: /** Underlying cache of documents and their read times. */ - immutable::SortedMap> + immutable::SortedMap< + model::DocumentKey, + std::pair> docs_; // This instance is owned by MemoryPersistence; avoid a retain cycle. diff --git a/Firestore/core/src/local/proto_sizer.cc b/Firestore/core/src/local/proto_sizer.cc index 05c954568ef..5550d7a3c64 100644 --- a/Firestore/core/src/local/proto_sizer.cc +++ b/Firestore/core/src/local/proto_sizer.cc @@ -20,7 +20,7 @@ #include "Firestore/Protos/nanopb/firestore/local/maybe_document.nanopb.h" #include "Firestore/core/src/model/document_key.h" -#include "Firestore/core/src/model/maybe_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/nanopb/byte_string.h" #include "Firestore/core/src/nanopb/message.h" @@ -28,13 +28,13 @@ namespace firebase { namespace firestore { namespace local { -using model::MaybeDocument; +using model::MutableDocument; ProtoSizer::ProtoSizer(LocalSerializer serializer) : serializer_(std::move(serializer)) { } -int64_t ProtoSizer::CalculateByteSize(const MaybeDocument& maybe_doc) const { +int64_t ProtoSizer::CalculateByteSize(const MutableDocument& maybe_doc) const { // TODO(varconst): implement a version of `nanopb::Writer` that only // calculates sizes without actually doing the encoding (to the extent // possible). This isn't high priority as long as `ProtoSizer` is only used in diff --git a/Firestore/core/src/local/proto_sizer.h b/Firestore/core/src/local/proto_sizer.h index a5daed169b0..7b01eeead43 100644 --- a/Firestore/core/src/local/proto_sizer.h +++ b/Firestore/core/src/local/proto_sizer.h @@ -33,7 +33,7 @@ class ProtoSizer : public Sizer { explicit ProtoSizer(LocalSerializer serializer); int64_t CalculateByteSize( - const model::MaybeDocument& maybe_doc) const override; + const model::MutableDocument& maybe_doc) const override; int64_t CalculateByteSize( const model::MutationBatch& mutation_batch) const override; diff --git a/Firestore/core/src/local/query_engine.cc b/Firestore/core/src/local/query_engine.cc index f8e7dabad3e..a45a298f4c3 100644 --- a/Firestore/core/src/local/query_engine.cc +++ b/Firestore/core/src/local/query_engine.cc @@ -23,7 +23,7 @@ #include "Firestore/core/src/local/local_documents_view.h" #include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_set.h" -#include "Firestore/core/src/model/maybe_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/snapshot_version.h" #include "Firestore/core/src/util/log.h" @@ -37,8 +37,7 @@ using model::Document; using model::DocumentKeySet; using model::DocumentMap; using model::DocumentSet; -using model::MaybeDocument; -using model::MaybeDocumentMap; +using model::MutableDocument; using model::SnapshotVersion; DocumentMap QueryEngine::GetDocumentsMatchingQuery( @@ -60,7 +59,7 @@ DocumentMap QueryEngine::GetDocumentsMatchingQuery( return ExecuteFullCollectionScan(query); } - MaybeDocumentMap documents = local_documents_view_->GetDocuments(remote_keys); + DocumentMap documents = local_documents_view_->GetDocuments(remote_keys); DocumentSet previous_results = ApplyQuery(query, documents); if (query.limit_type() != LimitType::None && @@ -82,24 +81,23 @@ DocumentMap QueryEngine::GetDocumentsMatchingQuery( // is already a DocumentMap. If a document is contained in both lists, then // its contents are the same. for (const Document& result : previous_results) { - updated_results = updated_results.insert(result.key(), result); + updated_results = updated_results.insert(result->key(), result); } return updated_results; } DocumentSet QueryEngine::ApplyQuery(const Query& query, - const MaybeDocumentMap& documents) const { + const DocumentMap& documents) const { // Sort the documents and re-apply the query filter since previously matching // documents do not necessarily still match the query. DocumentSet query_results(query.Comparator()); for (const auto& document_entry : documents) { - const MaybeDocument& maybe_doc = document_entry.second; - if (maybe_doc.is_document()) { - Document doc(maybe_doc); + const Document& doc = document_entry.second; + if (doc->is_found_document()) { if (query.Matches(doc)) { - query_results = query_results.insert(std::move(doc)); + query_results = query_results.insert(doc); } } } @@ -133,8 +131,8 @@ bool QueryEngine::NeedsRefill( // We don't need to refill the query if there were already no documents. return false; } - return document_at_limit_edge->has_pending_writes() || - document_at_limit_edge->version() > limbo_free_snapshot_version; + return (*document_at_limit_edge)->has_pending_writes() || + (*document_at_limit_edge)->version() > limbo_free_snapshot_version; } DocumentMap QueryEngine::ExecuteFullCollectionScan(const Query& query) { diff --git a/Firestore/core/src/local/query_engine.h b/Firestore/core/src/local/query_engine.h index 4f6f1ea4ab3..5b234667660 100644 --- a/Firestore/core/src/local/query_engine.h +++ b/Firestore/core/src/local/query_engine.h @@ -70,7 +70,7 @@ class QueryEngine { private: /** Applies the query filter and sorting to the provided documents. */ model::DocumentSet ApplyQuery(const core::Query& query, - const model::MaybeDocumentMap& documents) const; + const model::DocumentMap& documents) const; /** * Determines if a limit query needs to be refilled from cache, making it diff --git a/Firestore/core/src/local/query_result.h b/Firestore/core/src/local/query_result.h index acfb5017529..bd879f30146 100644 --- a/Firestore/core/src/local/query_result.h +++ b/Firestore/core/src/local/query_result.h @@ -21,7 +21,7 @@ #include #include "Firestore/core/src/model/document_key_set.h" -#include "Firestore/core/src/model/document_map.h" +#include "Firestore/core/src/model/model_fwd.h" namespace firebase { namespace firestore { diff --git a/Firestore/core/src/local/remote_document_cache.h b/Firestore/core/src/local/remote_document_cache.h index 62d324af2f5..1aa66051236 100644 --- a/Firestore/core/src/local/remote_document_cache.h +++ b/Firestore/core/src/local/remote_document_cache.h @@ -49,7 +49,7 @@ class RemoteDocumentCache { * @param document A Document or DeletedDocument to put in the cache. * @param read_time The time at which the document was read or committed. */ - virtual void Add(const model::MaybeDocument& document, + virtual void Add(const model::MutableDocument& document, const model::SnapshotVersion& read_time) = 0; /** Removes the cached entry for the given key (no-op if no entry exists). */ @@ -62,8 +62,7 @@ class RemoteDocumentCache { * @return The cached Document or DeletedDocument entry, or nullopt if we * have nothing cached. */ - virtual absl::optional Get( - const model::DocumentKey& key) = 0; + virtual model::MutableDocument Get(const model::DocumentKey& key) = 0; /** * Looks up a set of entries in the cache. @@ -72,7 +71,7 @@ class RemoteDocumentCache { * @return The cached Document or NoDocument entries indexed by key. If an * entry is not cached, the corresponding key will be mapped to a null value. */ - virtual model::OptionalMaybeDocumentMap GetAll( + virtual model::MutableDocumentMap GetAll( const model::DocumentKeySet& keys) = 0; /** @@ -88,7 +87,7 @@ class RemoteDocumentCache { * documents that have been read since this snapshot version (exclusive). * @return The set of matching documents. */ - virtual model::DocumentMap GetMatching( + virtual model::MutableDocumentMap GetMatching( const core::Query& query, const model::SnapshotVersion& since_read_time) = 0; }; diff --git a/Firestore/core/src/local/sizer.h b/Firestore/core/src/local/sizer.h index 51a9a36a45d..68d27298891 100644 --- a/Firestore/core/src/local/sizer.h +++ b/Firestore/core/src/local/sizer.h @@ -23,7 +23,7 @@ namespace firebase { namespace firestore { namespace model { -class MaybeDocument; +class MutableDocument; class MutationBatch; } // namespace model @@ -44,7 +44,7 @@ class Sizer { * NoDocuments have an associated size. */ virtual int64_t CalculateByteSize( - const model::MaybeDocument& maybe_doc) const = 0; + const model::MutableDocument& maybe_doc) const = 0; /** * Calculates the size of the given mutation_batch in bytes. diff --git a/Firestore/core/src/model/database_id.cc b/Firestore/core/src/model/database_id.cc index afee7c14805..63022d52e3d 100644 --- a/Firestore/core/src/model/database_id.cc +++ b/Firestore/core/src/model/database_id.cc @@ -18,8 +18,10 @@ #include +#include "Firestore/core/src/model/resource_path.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/hashing.h" +#include "absl/strings/string_view.h" namespace firebase { namespace firestore { @@ -34,6 +36,14 @@ DatabaseId::DatabaseId(std::string project_id, std::string database_id) { rep_ = std::make_shared(std::move(project_id), std::move(database_id)); } +DatabaseId DatabaseId::FromName(const std::string& name) { + auto resource_name = ResourcePath::FromString(name); + HARD_ASSERT(resource_name.size() > 3 && resource_name[0] == "projects" && + resource_name[2] == "databases", + "Tried to parse an invalid resource name: %s", name); + return DatabaseId{resource_name[1], resource_name[3]}; +} + util::ComparisonResult DatabaseId::CompareTo( const firebase::firestore::model::DatabaseId& rhs) const { util::ComparisonResult cmp = util::Compare(project_id(), rhs.project_id()); diff --git a/Firestore/core/src/model/database_id.h b/Firestore/core/src/model/database_id.h index 5cb2fc72bba..276511757b4 100644 --- a/Firestore/core/src/model/database_id.h +++ b/Firestore/core/src/model/database_id.h @@ -23,6 +23,7 @@ #include #include "Firestore/core/src/util/comparison.h" +#include "absl/strings/string_view.h" namespace firebase { namespace firestore { @@ -46,6 +47,9 @@ class DatabaseId : public util::Comparable { explicit DatabaseId(std::string project_id, std::string database_id = kDefault); + /** Returns a DatabaseId from a fully qualified resource name. */ + static DatabaseId FromName(const std::string& name); + const std::string& project_id() const { return rep_->project_id; } diff --git a/Firestore/core/src/model/delete_mutation.cc b/Firestore/core/src/model/delete_mutation.cc index 91ba76cff89..6716ffb7728 100644 --- a/Firestore/core/src/model/delete_mutation.cc +++ b/Firestore/core/src/model/delete_mutation.cc @@ -19,10 +19,8 @@ #include #include -#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/model/no_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/util/hard_assert.h" namespace firebase { @@ -41,12 +39,11 @@ DeleteMutation::DeleteMutation(const Mutation& mutation) : Mutation(mutation) { HARD_ASSERT(type() == Type::Delete); } -MaybeDocument DeleteMutation::Rep::ApplyToRemoteDocument( - const absl::optional& maybe_doc, - const MutationResult& mutation_result) const { - VerifyKeyMatches(maybe_doc); +void DeleteMutation::Rep::ApplyToRemoteDocument( + MutableDocument& document, const MutationResult& mutation_result) const { + VerifyKeyMatches(document); - HARD_ASSERT(mutation_result.transform_results() == absl::nullopt, + HARD_ASSERT(mutation_result.transform_results()->values_count == 0, "Transform results received by DeleteMutation."); // Unlike ApplyToLocalView, if we're applying a mutation to a remote document @@ -55,20 +52,17 @@ MaybeDocument DeleteMutation::Rep::ApplyToRemoteDocument( // We store the deleted document at the commit version of the delete. Any // document version that the server sends us before the delete was applied is // discarded. - return NoDocument(key(), mutation_result.version(), - /* has_committed_mutations= */ true); + document.ConvertToNoDocument(mutation_result.version()) + .SetHasCommittedMutations(); } -absl::optional DeleteMutation::Rep::ApplyToLocalView( - const absl::optional& maybe_doc, const Timestamp&) const { - VerifyKeyMatches(maybe_doc); +void DeleteMutation::Rep::ApplyToLocalView(MutableDocument& document, + const Timestamp&) const { + VerifyKeyMatches(document); - if (!precondition().IsValidFor(maybe_doc)) { - return maybe_doc; + if (precondition().IsValidFor(document)) { + document.ConvertToNoDocument(SnapshotVersion::None()); } - - return NoDocument(key(), SnapshotVersion::None(), - /* has_committed_mutations= */ false); } std::string DeleteMutation::Rep::ToString() const { diff --git a/Firestore/core/src/model/delete_mutation.h b/Firestore/core/src/model/delete_mutation.h index 130975f7599..a4fc3b576e8 100644 --- a/Firestore/core/src/model/delete_mutation.h +++ b/Firestore/core/src/model/delete_mutation.h @@ -52,13 +52,12 @@ class DeleteMutation : public Mutation { return Type::Delete; } - MaybeDocument ApplyToRemoteDocument( - const absl::optional& maybe_doc, + void ApplyToRemoteDocument( + MutableDocument& document, const MutationResult& mutation_result) const override; - absl::optional ApplyToLocalView( - const absl::optional& maybe_doc, - const Timestamp&) const override; + void ApplyToLocalView(MutableDocument& document, + const Timestamp&) const override; // Does not override Equals or Hash; Mutation's versions are sufficient. diff --git a/Firestore/core/src/model/document.cc b/Firestore/core/src/model/document.cc index 040fb8854e5..9f74aa5f764 100644 --- a/Firestore/core/src/model/document.cc +++ b/Firestore/core/src/model/document.cc @@ -17,163 +17,13 @@ #include "Firestore/core/src/model/document.h" #include -#include -#include - -#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" -#include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/nanopb/message.h" -#include "Firestore/core/src/nanopb/nanopb_util.h" -#include "Firestore/core/src/nanopb/reader.h" -#include "Firestore/core/src/util/hard_assert.h" namespace firebase { namespace firestore { namespace model { -static_assert( - sizeof(MaybeDocument) == sizeof(Document), - "Document may not have additional members (everything goes in Rep)"); - -class Document::Rep : public MaybeDocument::Rep { - public: - Rep(ObjectValue&& data, - DocumentKey&& key, - SnapshotVersion version, - DocumentState document_state) - : MaybeDocument::Rep(Type::Document, std::move(key), version), - data_(std::move(data)), - document_state_(document_state) { - } - - Rep(ObjectValue&& data, - DocumentKey&& key, - SnapshotVersion version, - DocumentState document_state, - absl::any proto) - : Rep(std::move(data), std::move(key), version, document_state) { - proto_ = std::move(proto); - } - - const ObjectValue& data() const { - return data_; - } - - DocumentState document_state() const { - return document_state_; - } - - bool has_local_mutations() const { - return document_state_ == DocumentState::kLocalMutations; - } - - bool has_committed_mutations() const { - return document_state_ == DocumentState::kCommittedMutations; - } - - bool has_pending_writes() const override { - return has_local_mutations() || has_committed_mutations(); - } - - bool Equals(const MaybeDocument::Rep& other) const override { - if (!MaybeDocument::Rep::Equals(other)) return false; - - const auto& other_rep = static_cast(other); - return document_state_ == other_rep.document_state_ && - data_ == other_rep.data_; - } - - size_t Hash() const override { - return util::Hash(MaybeDocument::Rep::Hash(), data_, document_state_); - } - - std::string ToString() const override { - return absl::StrCat( - "Document(key=", key().ToString(), ", version=", version().ToString(), - ", document_state=", document_state_, ", data=", data_.ToString(), ")"); - } - - private: - friend class Document; - - ObjectValue data_; - DocumentState document_state_; - absl::any proto_; -}; - -Document::Document(ObjectValue data, - DocumentKey key, - SnapshotVersion version, - DocumentState document_state) - : MaybeDocument(std::make_shared( - std::move(data), std::move(key), version, document_state)) { -} - -Document::Document(ObjectValue data, - DocumentKey key, - SnapshotVersion version, - DocumentState document_state, - absl::any proto) - : MaybeDocument(std::make_shared(std::move(data), - std::move(key), - version, - document_state, - std::move(proto))) { -} - -Document::Document(const MaybeDocument& document) : MaybeDocument(document) { - HARD_ASSERT(type() == Type::Document); -} - -const ObjectValue& Document::data() const { - return doc_rep().data(); -} - -absl::optional Document::field(const FieldPath& path) const { - return data().Get(path); -} - -DocumentState Document::document_state() const { - return doc_rep().document_state_; -} - -bool Document::has_local_mutations() const { - return doc_rep().has_local_mutations(); -} - -bool Document::has_committed_mutations() const { - return doc_rep().has_committed_mutations(); -} - -const absl::any& Document::proto() const { - return doc_rep().proto_; -} - -const Document::Rep& Document::doc_rep() const { - return static_cast(MaybeDocument::rep()); -} - -std::ostream& operator<<(std::ostream& os, DocumentState state) { - switch (state) { - case DocumentState::kCommittedMutations: - return os << "kCommittedMutations"; - case DocumentState::kLocalMutations: - return os << "kLocalMutations"; - case DocumentState::kSynced: - return os << "kLocalSynced"; - } - - UNREACHABLE(); -} - std::ostream& operator<<(std::ostream& os, const Document& doc) { - return os << doc.doc_rep().ToString(); -} - -/** Compares against another Document. */ -bool operator==(const Document& lhs, const Document& rhs) { - return lhs.doc_rep().Equals(rhs.doc_rep()); + return os << doc.ToString(); } } // namespace model diff --git a/Firestore/core/src/model/document.h b/Firestore/core/src/model/document.h index 18fa43591e2..ce4d12f1313 100644 --- a/Firestore/core/src/model/document.h +++ b/Firestore/core/src/model/document.h @@ -18,124 +18,54 @@ #define FIRESTORE_CORE_SRC_MODEL_DOCUMENT_H_ #include -#include #include +#include -#include "Firestore/core/src/model/maybe_document.h" -#include "absl/types/any.h" -#include "absl/types/optional.h" +#include "Firestore/core/src/model/mutable_document.h" namespace firebase { namespace firestore { - -typedef struct _google_firestore_v1_Document google_firestore_v1_Document; -typedef struct _google_firestore_v1_Value google_firestore_v1_Value; - -namespace bundle { -class BundleSerializer; -} // namespace bundle - -namespace local { -class LocalSerializer; -} // namespace local - -namespace nanopb { -class Reader; - -template -class Message; -} // namespace nanopb - -namespace remote { -class Serializer; -} // namespace remote - namespace model { -class FieldPath; -class FieldValue; -class ObjectValue; - -/** Describes the `has_pending_writes` state of a document. */ -enum class DocumentState { - /** - * Local mutations applied via the mutation queue. Document is potentially - * inconsistent. - */ - kLocalMutations, - - /** - * Mutations applied based on a write acknowledgment. Document is potentially - * inconsistent. - */ - kCommittedMutations, - - /** No mutations applied. Document was sent to us by Watch. */ - kSynced, -}; - -std::ostream& operator<<(std::ostream& os, DocumentState state); - -/** - * Represents a document in Firestore with a key, version, data and whether the - * data has local mutations applied to it. - */ -class Document : public MaybeDocument { - public: - Document(ObjectValue data, - DocumentKey key, - SnapshotVersion version, - DocumentState document_state); - - private: - // TODO(b/146372592): Make this public once we can use Abseil across - // iOS/public C++ library boundaries. - friend class remote::Serializer; - friend class bundle::BundleSerializer; - - Document(ObjectValue data, - DocumentKey key, - SnapshotVersion version, - DocumentState document_state, - absl::any proto); - +/** Represents an immutable document in Firestore. */ +class Document { public: - /** - * Casts a MaybeDocument to a Document. This is a checked operation that will - * assert if the type of the MaybeDocument isn't actually Type::Document. - */ - explicit Document(const MaybeDocument& document); + Document(MutableDocument document) // NOLINT(runtime/explicit) + : document_{std::move(document)} { + } - /** Creates an invalid Document instance. */ Document() = default; - const ObjectValue& data() const; - - absl::optional field(const FieldPath& path) const; - - DocumentState document_state() const; + const MutableDocument& get() const { + return document_; + } - bool has_local_mutations() const; + const MutableDocument* operator->() const { + return &document_; + } - bool has_committed_mutations() const; + size_t Hash() const { + return document_.Hash(); + } - const absl::any& proto() const; - - /** Compares against another Document. */ - friend bool operator==(const Document& lhs, const Document& rhs); - - friend std::ostream& operator<<(std::ostream& os, const Document& doc); + std::string ToString() const { + return document_.ToString(); + } private: - class Rep; - - const Rep& doc_rep() const; + MutableDocument document_; }; +inline bool operator==(const Document& lhs, const Document& rhs) { + return lhs.get() == rhs.get(); +} + inline bool operator!=(const Document& lhs, const Document& rhs) { return !(lhs == rhs); } +std::ostream& operator<<(std::ostream& os, const Document& doc); + } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/model/document_key.cc b/Firestore/core/src/model/document_key.cc index f5fd8c73d82..a8da934347f 100644 --- a/Firestore/core/src/model/document_key.cc +++ b/Firestore/core/src/model/document_key.cc @@ -50,13 +50,22 @@ DocumentKey::DocumentKey(ResourcePath&& path) } DocumentKey DocumentKey::FromPathString(const std::string& path) { - return DocumentKey{ResourcePath::FromStringView(path)}; + return DocumentKey{ResourcePath::FromString(path)}; } DocumentKey DocumentKey::FromSegments(std::initializer_list list) { return DocumentKey{ResourcePath{list}}; } +DocumentKey DocumentKey::FromName(const std::string& name) { + auto resource_name = ResourcePath::FromString(name); + HARD_ASSERT(resource_name.size() > 4 && resource_name[0] == "projects" && + resource_name[2] == "databases" && + resource_name[4] == "documents", + "Tried to parse an invalid key: %s", name); + return DocumentKey{resource_name.PopFirst(5)}; +} + const DocumentKey& DocumentKey::Empty() { static const DocumentKey* empty = new DocumentKey(); return *empty; diff --git a/Firestore/core/src/model/document_key.h b/Firestore/core/src/model/document_key.h index 9becf3841fe..2b17cb7251a 100644 --- a/Firestore/core/src/model/document_key.h +++ b/Firestore/core/src/model/document_key.h @@ -23,6 +23,8 @@ #include #include +#include "absl/strings/string_view.h" + namespace firebase { namespace firestore { @@ -57,6 +59,9 @@ class DocumentKey { /** Creates and returns a new document key with the given segments. */ static DocumentKey FromSegments(std::initializer_list list); + /** Returns a DocumentKey from a fully qualified resource name. */ + static DocumentKey FromName(const std::string& name); + /** Returns a shared instance of an empty document key. */ static const DocumentKey& Empty(); diff --git a/Firestore/core/src/model/document_map.cc b/Firestore/core/src/model/document_map.cc deleted file mode 100644 index b82aa2cc201..00000000000 --- a/Firestore/core/src/model/document_map.cc +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2019 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/model/document_map.h" - -namespace firebase { -namespace firestore { -namespace model { - -ABSL_MUST_USE_RESULT DocumentMap -DocumentMap::insert(const DocumentKey& key, const Document& value) const { - return DocumentMap{map_.insert(key, value)}; -} - -ABSL_MUST_USE_RESULT DocumentMap -DocumentMap::erase(const DocumentKey& key) const { - return DocumentMap{map_.erase(key)}; -} - -} // namespace model -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/src/model/document_map.h b/Firestore/core/src/model/document_map.h deleted file mode 100644 index 51885ad0f53..00000000000 --- a/Firestore/core/src/model/document_map.h +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * 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_MODEL_DOCUMENT_MAP_H_ -#define FIRESTORE_CORE_SRC_MODEL_DOCUMENT_MAP_H_ - -#include - -#include "Firestore/core/src/immutable/sorted_map.h" -#include "Firestore/core/src/model/document.h" -#include "Firestore/core/src/model/document_key.h" -#include "Firestore/core/src/model/maybe_document.h" -#include "absl/base/attributes.h" -#include "absl/types/optional.h" - -namespace firebase { -namespace firestore { -namespace model { - -/** - * Convenience type for a map of keys to MaybeDocuments, since they are so - * common. - */ -using MaybeDocumentMap = immutable::SortedMap; - -using OptionalMaybeDocumentMap = - immutable::SortedMap>; - -/** - * Convenience type for a map of keys to Documents, since they are so common. - * - * PORTING NOTE: unlike other platforms, in C++ `Foo` cannot be - * converted to `Foo`; consequently, if `DocumentMap` were simply an - * alias similar to `MaybeDocumentMap`, it couldn't be passed to functions - * expecting `MaybeDocumentMap`. - * - * To work around this, in C++ `DocumentMap` is a simple wrapper over a - * `MaybeDocumentMap` that forwards all functions to the underlying map but with - * added type safety (it only accepts `Document`, not `MaybeDocument`). Use - * `DocumentMap` in functions creating and/or returning maps that only contain - * `Document`; when the `DocumentMap` needs to be passed to a function accepting - * a `MaybeDocumentMap`, use `underlying_map` function to get (read-only) access - * to the representation. Also use `underlying_map` for iterating and searching. - */ -class DocumentMap { - public: - using key_type = DocumentKey; - using mapped_type = Document; - - DocumentMap() = default; - - ABSL_MUST_USE_RESULT DocumentMap insert(const DocumentKey& key, - const Document& value) const; - - ABSL_MUST_USE_RESULT DocumentMap erase(const DocumentKey& key) const; - - bool empty() const { - return map_.empty(); - } - MaybeDocumentMap::size_type size() const { - return map_.size(); - } - - /** Use this function to "convert" `DocumentMap` to a `MaybeDocumentMap`. */ - const MaybeDocumentMap& underlying_map() const { - return map_; - } - - private: - explicit DocumentMap(MaybeDocumentMap&& map) : map_{std::move(map)} { - } - - MaybeDocumentMap map_; -}; - -} // namespace model -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_SRC_MODEL_DOCUMENT_MAP_H_ diff --git a/Firestore/core/src/model/document_set.cc b/Firestore/core/src/model/document_set.cc index e108762e81c..18c4d77a994 100644 --- a/Firestore/core/src/model/document_set.cc +++ b/Firestore/core/src/model/document_set.cc @@ -38,7 +38,7 @@ inline absl::optional none() { DocumentComparator DocumentComparator::ByKey() { return DocumentComparator([](const Document& lhs, const Document& rhs) { - return util::Compare(lhs.key(), rhs.key()); + return util::Compare(lhs->key(), rhs->key()); }); } @@ -63,14 +63,13 @@ size_t DocumentSet::Hash() const { } bool DocumentSet::ContainsKey(const DocumentKey& key) const { - return index_.underlying_map().find(key) != index_.underlying_map().end(); + return index_.find(key) != index_.end(); } absl::optional DocumentSet::GetDocument( const DocumentKey& key) const { - auto found = index_.underlying_map().find(key); - return found != index_.underlying_map().end() ? Document(found->second) - : none(); + auto found = index_.find(key); + return found != index_.end() ? Document(found->second) : none(); } absl::optional DocumentSet::GetFirstDocument() const { @@ -97,7 +96,7 @@ DocumentSet DocumentSet::insert( // Remove any prior mapping of the document's key before adding, preventing // the sorted_set_ from accumulating values that aren't in the index. - const DocumentKey& key = document->key(); + const DocumentKey& key = (*document)->key(); DocumentSet removed = erase(key); DocumentMap index = removed.index_.insert(key, *document); diff --git a/Firestore/core/src/model/document_set.h b/Firestore/core/src/model/document_set.h index 5d66f249dc3..ba204dea50c 100644 --- a/Firestore/core/src/model/document_set.h +++ b/Firestore/core/src/model/document_set.h @@ -18,6 +18,7 @@ #define FIRESTORE_CORE_SRC_MODEL_DOCUMENT_SET_H_ #include +#include #include #include #include @@ -25,7 +26,7 @@ #include "Firestore/core/src/immutable/sorted_container.h" #include "Firestore/core/src/immutable/sorted_set.h" #include "Firestore/core/src/model/document.h" -#include "Firestore/core/src/model/document_map.h" +#include "Firestore/core/src/model/model_fwd.h" #include "Firestore/core/src/util/comparison.h" namespace firebase { diff --git a/Firestore/core/src/model/field_mask.cc b/Firestore/core/src/model/field_mask.cc index 20d63d914cc..f2a6df87533 100644 --- a/Firestore/core/src/model/field_mask.cc +++ b/Firestore/core/src/model/field_mask.cc @@ -16,7 +16,6 @@ #include "Firestore/core/src/model/field_mask.h" -#include "Firestore/core/src/model/field_value.h" #include "Firestore/core/src/util/hashing.h" namespace firebase { diff --git a/Firestore/core/src/model/field_value.cc b/Firestore/core/src/model/field_value.cc deleted file mode 100644 index b740f016a99..00000000000 --- a/Firestore/core/src/model/field_value.cc +++ /dev/null @@ -1,853 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * 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/model/field_value.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Firestore/core/src/immutable/sorted_map.h" -#include "Firestore/core/src/model/field_mask.h" -#include "Firestore/core/src/nanopb/byte_string.h" -#include "Firestore/core/src/timestamp_internal.h" -#include "Firestore/core/src/util/comparison.h" -#include "Firestore/core/src/util/hard_assert.h" -#include "Firestore/core/src/util/hashing.h" -#include "Firestore/core/src/util/to_string.h" -#include "absl/algorithm/container.h" -#include "absl/base/casts.h" -#include "absl/memory/memory.h" -#include "absl/strings/escaping.h" - -namespace firebase { -namespace firestore { -namespace model { -namespace { - -using BaseValue = FieldValue::BaseValue; -using Reference = FieldValue::Reference; -using ServerTimestamp = FieldValue::ServerTimestamp; -using Type = FieldValue::Type; - -using nanopb::ByteString; -using util::Compare; -using util::CompareContainer; -using util::ComparisonResult; - -template -const T& Cast(const BaseValue& rep) { - return static_cast(rep); -} - -class NullValue : public FieldValue::BaseValue { - public: - Type type() const override { - return Type::Null; - } - - std::string ToString() const override { - return util::ToString(nullptr); - } - - bool Equals(const BaseValue& other) const override { - if (type() != other.type()) return false; - - // NullValue is the only instance of itself - return true; - } - - ComparisonResult CompareTo(const BaseValue& other) const override { - ComparisonResult cmp = CompareTypes(other); - if (!util::Same(cmp)) return cmp; - - // Null is only comparable with itself and is defined to be the same. - return ComparisonResult::Same; - } - - size_t Hash() const override { - // std::hash is not defined for nullptr_t. - return util::Hash(static_cast(nullptr)); - } -}; - -/** - * A base class for implementing a "simple" field value type. Simple field - * values: - * - * * Are only comparable with values of their own type - * * Can be implemented by delegating to standard utilities, e.g. ToString() - * by calling util::ToString. - */ -template -class SimpleFieldValue : public FieldValue::BaseValue { - public: - explicit SimpleFieldValue(ValueType value) : value_(std::move(value)) { - } - - Type type() const override { - return type_enum; - } - - std::string ToString() const override { - return util::ToString(value_); - } - - bool Equals(const BaseValue& other) const override { - if (type() != other.type()) return false; - - auto& other_value = Cast(other); - return value_ == other_value.value(); - } - - ComparisonResult CompareTo(const BaseValue& other) const override { - ComparisonResult cmp = CompareTypes(other); - if (!util::Same(cmp)) return cmp; - - auto& other_value = Cast(other); - return Compare(value_, other_value.value()); - } - - size_t Hash() const override { - return util::Hash(value_); - } - - const ValueType& value() const { - return value_; - } - - private: - ValueType value_; -}; - -class BooleanValue : public SimpleFieldValue { - public: - using SimpleFieldValue::SimpleFieldValue; -}; - -template -class NumberValue : public SimpleFieldValue { - public: - using SimpleFieldValue::SimpleFieldValue; - - ComparisonResult CompareTo(const BaseValue& other) const override; -}; - -class IntegerValue : public NumberValue { - public: - using NumberValue::NumberValue; -}; - -int64_t Integer(const BaseValue& rep) { - return Cast(rep).value(); -} - -class DoubleValue : public NumberValue { - public: - using NumberValue::NumberValue; - - bool Equals(const BaseValue& other) const override { - if (type() != other.type()) return false; - - auto& other_value = Cast(other); - return util::DoubleBitwiseEquals(value(), other_value.value()); - } - - size_t Hash() const override { - return util::DoubleBitwiseHash(value()); - } -}; - -double Double(const BaseValue& rep) { - return Cast(rep).value(); -} - -template -ComparisonResult NumberValue::CompareTo( - const BaseValue& other) const { - ComparisonResult cmp = this->CompareTypes(other); - if (!util::Same(cmp)) return cmp; - - Type this_type = this->type(); - Type other_type = other.type(); - - if (this_type == other_type) { - if (this_type == Type::Integer) { - return Compare(Integer(*this), Integer(other)); - } else { - return Compare(Double(*this), Double(other)); - } - - } else { - if (this_type == Type::Integer) { - // CompareMixedNumber only takes (double, int64_t) so reverse the argument - // order and then reverse the result. - return util::ReverseOrder( - util::CompareMixedNumber(Double(other), Integer(*this))); - } else { - return util::CompareMixedNumber(Double(*this), Integer(other)); - } - } -} - -// TODO(wilhuff): Use SimpleFieldValue as a base once we migrate to absl::Hash. -// -// This can't extend SimpleFieldValue because `util::Hash` is undefined for -// Timestamp (and you can't override a compile-time error in a base class out -// of existence). absl::Hash allows us to implement hashing in a way that -// requires no public declaration of conformance. -class TimestampValue : public BaseValue { - public: - explicit TimestampValue(Timestamp value) : value_(value) { - } - - Type type() const override { - return Type::Timestamp; - } - - std::string ToString() const override { - return util::ToString(value_); - } - - bool Equals(const BaseValue& other) const override { - if (type() != other.type()) return false; - - auto& other_value = Cast(other); - return value_ == other_value.value_; - } - - ComparisonResult CompareTo(const BaseValue& other) const override { - ComparisonResult cmp = CompareTypes(other); - if (!util::Same(cmp)) return cmp; - - if (other.type() == Type::Timestamp) { - return Compare(value_, Cast(other).value_); - } else { - return ComparisonResult::Ascending; - } - } - - size_t Hash() const override { - return TimestampInternal::Hash(value()); - } - - const Timestamp& value() const { - return value_; - } - - private: - Timestamp value_; -}; - -/** - * Represents a locally-applied Server Timestamp. - * - * Notes: - * - ServerTimestampValue instances are created as the result of applying a - * field transform. They can only exist in the local view of a document. - * Therefore they do not need to be parsed or serialized. - * - When evaluated locally (e.g. via DocumentSnapshot data), they by default - * evaluate to null. - * - This behavior can be configured by passing custom FieldValueOptions to - * `valueWithOptions:`. - * - They sort after all Timestamp values. With respect to other - * ServerTimestampValues, they sort by their local_write_time. - */ -class ServerTimestampValue : public FieldValue::BaseValue { - public: - explicit ServerTimestampValue(ServerTimestamp server_timestamp) - : server_timestamp_(std::move(server_timestamp)) { - } - - Type type() const override { - return Type::ServerTimestamp; - } - - std::string ToString() const override { - std::string time = value().local_write_time().ToString(); - return absl::StrCat("ServerTimestamp(local_write_time=", time, ")"); - } - - bool Equals(const BaseValue& other) const override { - if (type() != other.type()) return false; - - auto& other_value = Cast(other); - return value().local_write_time() == other_value.value().local_write_time(); - } - - ComparisonResult CompareTo(const BaseValue& other) const override { - ComparisonResult cmp = CompareTypes(other); - if (!util::Same(cmp)) return cmp; - - if (other.type() == Type::ServerTimestamp) { - return Compare( - value().local_write_time(), - Cast(other).value().local_write_time()); - } else { - return ComparisonResult::Descending; - } - } - - size_t Hash() const override { - size_t result = TimestampInternal::Hash(value().local_write_time()); - if (value().previous_value()) { - result = util::Hash(result, *value().previous_value()); - } - return result; - } - - const ServerTimestamp& value() const { - return server_timestamp_; - } - - private: - ServerTimestamp server_timestamp_; -}; - -class StringValue : public SimpleFieldValue { - public: - using SimpleFieldValue::SimpleFieldValue; -}; - -class BlobValue : public SimpleFieldValue { - public: - using SimpleFieldValue::SimpleFieldValue; -}; - -class ReferenceValue : public FieldValue::BaseValue { - public: - explicit ReferenceValue(Reference reference) - : reference_(std::move(reference)) { - } - - Type type() const override { - return Type::Reference; - } - - bool Equals(const BaseValue& other) const override { - if (type() != other.type()) return false; - - auto& other_value = Cast(other); - return database_id() == other_value.database_id() && - key() == other_value.key(); - } - - ComparisonResult CompareTo(const BaseValue& other) const override { - ComparisonResult cmp = CompareTypes(other); - if (!util::Same(cmp)) return cmp; - - auto& other_value = Cast(other); - cmp = Compare(database_id(), other_value.database_id()); - if (!util::Same(cmp)) return cmp; - - return Compare(key(), other_value.key()); - } - - std::string ToString() const override { - return absl::StrCat("Reference(key=", key().ToString(), ")"); - } - - size_t Hash() const override { - return util::Hash(database_id(), key()); - } - - const Reference& value() const { - return reference_; - } - - const DatabaseId& database_id() const { - return reference_.database_id(); - } - - const DocumentKey& key() const { - return reference_.key(); - } - - private: - Reference reference_; -}; - -class GeoPointValue : public BaseValue { - public: - explicit GeoPointValue(GeoPoint value) : value_(value) { - } - - Type type() const override { - return Type::GeoPoint; - } - - std::string ToString() const override { - return util::ToString(value_); - } - - bool Equals(const BaseValue& other) const override { - if (type() != other.type()) return false; - - auto& other_value = Cast(other); - return value_ == other_value.value_; - } - - ComparisonResult CompareTo(const BaseValue& other) const override { - ComparisonResult cmp = CompareTypes(other); - if (!util::Same(cmp)) return cmp; - - auto& other_value = Cast(other); - return Compare(value_, other_value.value_); - } - - size_t Hash() const override { - return util::Hash(value_.latitude(), value_.longitude()); - } - - const GeoPoint& value() const { - return value_; - } - - private: - GeoPoint value_; -}; - -class ArrayContents : public FieldValue::BaseValue { - public: - explicit ArrayContents(FieldValue::Array value) : value_(std::move(value)) { - } - - Type type() const override { - return Type::Array; - } - - bool Equals(const BaseValue& other) const override { - if (type() != other.type()) return false; - - auto& other_value = Cast(other); - return absl::c_equal(value_, other_value.value_); - } - - ComparisonResult CompareTo(const BaseValue& other) const override { - ComparisonResult cmp = CompareTypes(other); - if (!util::Same(cmp)) return cmp; - - auto& other_value = Cast(other); - return util::CompareContainer(value_, other_value.value_); - } - - std::string ToString() const override { - return util::ToString(value_); - } - - size_t Hash() const override { - return util::Hash(value_); - } - - const FieldValue::Array& value() const { - return value_; - } - - private: - FieldValue::Array value_; -}; - -class MapContents : public FieldValue::BaseValue { - public: - explicit MapContents(FieldValue::Map value) : value_(std::move(value)) { - } - - Type type() const override { - return Type::Object; - } - - bool Equals(const BaseValue& other) const override { - if (type() != other.type()) return false; - - auto& other_value = Cast(other); - return absl::c_equal(value_, other_value.value_); - } - - ComparisonResult CompareTo(const BaseValue& other) const override { - ComparisonResult cmp = CompareTypes(other); - if (!util::Same(cmp)) return cmp; - - auto& other_value = Cast(other); - return util::CompareContainer(value_, other_value.value_); - } - - std::string ToString() const override { - return util::ToString(value_); - } - - size_t Hash() const override { - size_t result = 0; - for (auto&& entry : value_) { - result = util::Hash(result, entry.first, entry.second); - } - return result; - } - - const FieldValue::Map& value() const { - return value_; - } - - private: - FieldValue::Map value_; -}; - -} // namespace - -FieldValue::FieldValue() : FieldValue(std::make_shared()) { -} - -bool FieldValue::Comparable(Type lhs, Type rhs) { - switch (lhs) { - case Type::Integer: - case Type::Double: - return rhs == Type::Integer || rhs == Type::Double; - case Type::Timestamp: - case Type::ServerTimestamp: - return rhs == Type::Timestamp || rhs == Type::ServerTimestamp; - default: - return lhs == rhs; - } -} - -bool FieldValue::boolean_value() const { - HARD_ASSERT(type() == Type::Boolean); - return Cast(*rep_).value(); -} - -int64_t FieldValue::integer_value() const { - HARD_ASSERT(type() == Type::Integer); - return Cast(*rep_).value(); -} - -double FieldValue::double_value() const { - HARD_ASSERT(type() == Type::Double); - return Cast(*rep_).value(); -} - -Timestamp FieldValue::timestamp_value() const { - HARD_ASSERT(type() == Type::Timestamp); - return Cast(*rep_).value(); -} - -const ServerTimestamp& FieldValue::server_timestamp_value() const { - HARD_ASSERT(type() == Type::ServerTimestamp); - return Cast(*rep_).value(); -} - -const std::string& FieldValue::string_value() const { - HARD_ASSERT(type() == Type::String); - return Cast(*rep_).value(); -} - -const ByteString& FieldValue::blob_value() const { - HARD_ASSERT(type() == Type::Blob); - return Cast(*rep_).value(); -} - -const Reference& FieldValue::reference_value() const { - HARD_ASSERT(type() == Type::Reference); - return Cast(*rep_).value(); -} - -const GeoPoint& FieldValue::geo_point_value() const { - HARD_ASSERT(type() == Type::GeoPoint); - return Cast(*rep_).value(); -} - -const FieldValue::Array& FieldValue::array_value() const { - HARD_ASSERT(type() == Type::Array); - return Cast(*rep_).value(); -} - -const FieldValue::Map& FieldValue::object_value() const { - HARD_ASSERT(type() == Type::Object); - return Cast(*rep_).value(); -} - -// TODO(rsgowman): Reorder this file to match its header. -ObjectValue ObjectValue::Set(const FieldPath& field_path, - const FieldValue& value) const { - HARD_ASSERT(!field_path.empty(), - "Cannot set field for empty path on FieldValue"); - - // Set the value by recursively calling on child object. - const std::string& child_name = field_path.first_segment(); - if (field_path.size() == 1) { - // Recursive base case: - return SetChild(child_name, value); - } else { - // Nested path. Recursively generate a new sub-object and then wrap a new - // ObjectValue around the result. - ObjectValue child = ObjectValue::Empty(); - const FieldValue::Map& entries = fv_.object_value(); - const auto iter = entries.find(child_name); - if (iter != entries.end() && iter->second.type() == Type::Object) { - child = ObjectValue(iter->second); - } - ObjectValue new_child = child.Set(field_path.PopFirst(), value); - return SetChild(child_name, new_child.fv_); - } -} - -ObjectValue ObjectValue::Delete(const FieldPath& field_path) const { - HARD_ASSERT(!field_path.empty(), - "Cannot delete field for empty path on FieldValue"); - // Delete the value by recursively calling on child object. - const std::string& child_name = field_path.first_segment(); - if (field_path.size() == 1) { - return ObjectValue::FromMap(fv_.object_value().erase(child_name)); - } else { - const FieldValue::Map& entries = fv_.object_value(); - const auto iter = entries.find(child_name); - if (iter != entries.end() && iter->second.type() == Type::Object) { - ObjectValue new_child = - ObjectValue(iter->second).Delete(field_path.PopFirst()); - return SetChild(child_name, new_child.fv_); - } else { - // If the found value isn't an object, it cannot contain the remaining - // segments of the path. We don't actually change a primitive value to - // an object for a delete. - return *this; - } - } -} - -absl::optional ObjectValue::Get(const FieldPath& field_path) const { - const FieldValue* current = &this->fv_; - for (const auto& path : field_path) { - if (current->type() != Type::Object) { - return absl::nullopt; - } - - const FieldValue::Map& entries = current->object_value(); - const auto iter = entries.find(path); - if (iter == entries.end()) { - return absl::nullopt; - } else { - current = &iter->second; - } - } - return *current; -} - -FieldMask ObjectValue::ToFieldMask() const { - std::set fields; - - for (FieldValue::Map::const_iterator iter = fv_.object_value().begin(); - iter != fv_.object_value().end(); ++iter) { - FieldPath current_path{iter->first}; - FieldValue value = iter->second; - - if (value.type() == Type::Object) { - ObjectValue nested_map{value}; - FieldMask nested_mask = nested_map.ToFieldMask(); - if (nested_mask.size() == 0) { - // Preserve the empty map by adding it to the FieldMask. - fields.insert(current_path); - } else { - // For nested and non-empty ObjectValues, add the FieldPath of the leaf - // nodes. - for (const FieldPath& nested_path : nested_mask) { - fields.insert(current_path.Append(nested_path)); - } - } - } else { - fields.insert(current_path); - } - } - return FieldMask(fields); -} - -ObjectValue ObjectValue::SetChild(const std::string& child_name, - const FieldValue& value) const { - return ObjectValue::FromMap(fv_.object_value().insert(child_name, value)); -} - -FieldValue FieldValue::Null() { - return FieldValue(); -} - -FieldValue FieldValue::True() { - return FieldValue(std::make_shared(true)); -} - -FieldValue FieldValue::False() { - return FieldValue(std::make_shared(false)); -} - -FieldValue FieldValue::FromBoolean(bool value) { - return value ? True() : False(); -} - -FieldValue FieldValue::Nan() { - return FieldValue::FromDouble(NAN); -} - -FieldValue FieldValue::EmptyObject() { - return FieldValue::FromMap(FieldValue::Map()); -} - -FieldValue FieldValue::FromInteger(int64_t value) { - return FieldValue(std::make_shared(value)); -} - -// We use a canonical NaN bit pattern that's common for both Objective-C and -// Java. Specifically: -// -// - sign: 0 -// - exponent: 11 bits, all 1 -// - significand: 52 bits, MSB=1, rest=0 -// -// This matches the Firestore backend which uses Double.doubleToLongBits from -// the JDK (which is defined to normalize all NaNs to this value). This also -// happens to be a common value for NAN in C++, but C++ does not require this -// specific NaN value to be used, so we normalize. -const uint64_t kCanonicalNanBits = 0x7ff8000000000000ULL; - -FieldValue FieldValue::FromDouble(double value) { - static double canonical_nan = absl::bit_cast(kCanonicalNanBits); - if (std::isnan(value)) { - value = canonical_nan; - } - - return FieldValue(std::make_shared(value)); -} - -FieldValue FieldValue::FromTimestamp(const Timestamp& value) { - return FieldValue(std::make_shared(value)); -} - -FieldValue FieldValue::FromServerTimestamp(const Timestamp& local_write_time) { - return FromServerTimestamp(local_write_time, absl::nullopt); -} - -FieldValue FieldValue::FromServerTimestamp( - const Timestamp& local_write_time, - absl::optional previous_value) { - return FieldValue(std::make_shared( - ServerTimestamp(local_write_time, std::move(previous_value)))); -} - -FieldValue FieldValue::FromString(const char* value) { - return FieldValue(std::make_shared(value)); -} - -FieldValue FieldValue::FromString(const std::string& value) { - return FieldValue(std::make_shared(value)); -} - -FieldValue FieldValue::FromString(std::string&& value) { - return FieldValue(std::make_shared(std::move(value))); -} - -FieldValue FieldValue::FromBlob(ByteString blob) { - return FieldValue(std::make_shared(std::move(blob))); -} - -FieldValue FieldValue::FromReference(DatabaseId database_id, DocumentKey key) { - return FieldValue(std::make_shared( - Reference(std::move(database_id), std::move(key)))); -} - -FieldValue FieldValue::FromGeoPoint(const GeoPoint& value) { - return FieldValue(std::make_shared(value)); -} - -FieldValue FieldValue::FromArray(const Array& value) { - return FieldValue(std::make_shared(value)); -} - -FieldValue FieldValue::FromArray(Array&& value) { - return FieldValue(std::make_shared(std::move(value))); -} - -FieldValue FieldValue::FromMap(const Map& value) { - return FieldValue(std::make_shared(value)); -} - -FieldValue FieldValue::FromMap(FieldValue::Map&& value) { - return FieldValue(std::make_shared(std::move(value))); -} - -bool operator==(const FieldValue& lhs, const FieldValue& rhs) { - return lhs.rep_->Equals(*rhs.rep_); -} - -std::ostream& operator<<(std::ostream& os, const FieldValue& value) { - return os << value.ToString(); -} - -ComparisonResult FieldValue::BaseValue::CompareTypes( - const BaseValue& other) const { - Type this_type = type(); - Type other_type = other.type(); - - // This does not necessarily mean the types are actually the same. For those - // types that allow mixed types they'll need to handle this further. - if (FieldValue::Comparable(this_type, other_type)) { - return ComparisonResult::Same; - } - - // Otherwise, the types themselves are defined in order. - return Compare(this_type, other_type); -} - -// Default construction is insufficient because FieldValue's default constructor -// would make this have Type::Null, which then blows up when you try to Set -// on it. -ObjectValue::ObjectValue() : fv_(FieldValue::EmptyObject()) { -} - -ObjectValue::ObjectValue(FieldValue fv) : fv_(std::move(fv)) { - HARD_ASSERT(fv_.type() == FieldValue::Type::Object); -} - -ObjectValue ObjectValue::FromMap(const FieldValue::Map& value) { - return ObjectValue(FieldValue::FromMap(value)); -} - -ObjectValue ObjectValue::FromMap(FieldValue::Map&& value) { - return ObjectValue(FieldValue::FromMap(std::move(value))); -} - -ComparisonResult ObjectValue::CompareTo(const ObjectValue& rhs) const { - return fv_.CompareTo(rhs.fv_); -} - -const FieldValue::Map& ObjectValue::GetInternalValue() const { - return fv_.object_value(); -} - -std::string ObjectValue::ToString() const { - return fv_.ToString(); -} - -std::ostream& operator<<(std::ostream& os, const ObjectValue& value) { - return os << value.ToString(); -} - -size_t ObjectValue::Hash() const { - return fv_.Hash(); -} - -} // namespace model -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/src/model/field_value.h b/Firestore/core/src/model/field_value.h deleted file mode 100644 index 6cb429257a1..00000000000 --- a/Firestore/core/src/model/field_value.h +++ /dev/null @@ -1,441 +0,0 @@ -/* - * Copyright 2018 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FIRESTORE_CORE_SRC_MODEL_FIELD_VALUE_H_ -#define FIRESTORE_CORE_SRC_MODEL_FIELD_VALUE_H_ - -#include -#include -#include -#include -#include -#include -#include - -#include "Firestore/core/include/firebase/firestore/geo_point.h" -#include "Firestore/core/include/firebase/firestore/timestamp.h" -#include "Firestore/core/src/immutable/sorted_map.h" -#include "Firestore/core/src/model/database_id.h" -#include "Firestore/core/src/model/document_key.h" -#include "absl/base/attributes.h" -#include "absl/types/optional.h" - -namespace firebase { -namespace firestore { - -namespace nanopb { -class ByteString; -} // namespace nanopb - -namespace model { - -class FieldMask; -class FieldPath; -class ObjectValue; - -/** - * tagged-union class representing an immutable data value as stored in - * Firestore. FieldValue represents all the different kinds of values - * that can be stored in fields in a document. - */ -class FieldValue { - public: - class Reference; - class ServerTimestamp; - using Array = std::vector; - using Map = immutable::SortedMap; - - /** - * All the different kinds of values that can be stored in fields in - * a document. The types of the same comparison order should be defined - * together as a group. The order of each group is defined by the Firestore - * backend and is available at: - * https://firebase.google.com/docs/firestore/manage-data/data-types - */ - enum class Type { - Null, // Null - Boolean, // Boolean - Integer, // Number type starts here - Double, - Timestamp, // Timestamp type starts here - ServerTimestamp, - String, // String - Blob, // Blob - Reference, // Reference - GeoPoint, // GeoPoint - Array, // Array - Object, // Object - // New enum should not always been added at the tail. Add it to the correct - // position instead, see the doc comment above. - }; - - FieldValue(); - - FieldValue(const ObjectValue& object); // NOLINT(runtime/explicit) - - /** Returns the true type for this value. */ - Type type() const { - return rep_->type(); - } - - bool is_boolean() const { - return type() == Type::Boolean; - } - - bool is_integer() const { - return type() == Type::Integer; - } - - bool is_double() const { - return type() == Type::Double; - } - - bool is_timestamp() const { - return type() == Type::Timestamp; - } - - bool is_server_timestamp() const { - return type() == Type::ServerTimestamp; - } - - bool is_string() const { - return type() == Type::String; - } - - bool is_blob() const { - return type() == Type::Blob; - } - - bool is_reference() const { - return type() == Type::Reference; - } - - bool is_geo_point() const { - return type() == Type::GeoPoint; - } - - bool is_array() const { - return type() == Type::Array; - } - - bool is_object() const { - return type() == Type::Object; - } - - /** - * Checks if the given type is a numeric, such as Type::Integer or - * Type::Double. - */ - bool is_number() const { - Type t = type(); - return t == Type::Integer || t == Type::Double; - } - - /** - * PORTING NOTE: This deviates from the other platforms that define TypeOrder. - * Since we already define Type for union types, we use it together with this - * function to achieve the equivalent order of types i.e. - * i) if two types are comparable, then they are of equal order; - * ii) otherwise, their order is the same as the order of their Type. - */ - static bool Comparable(Type lhs, Type rhs); - - bool boolean_value() const; - - int64_t integer_value() const; - - double double_value() const; - - Timestamp timestamp_value() const; - - const ServerTimestamp& server_timestamp_value() const; - - const std::string& string_value() const; - - const nanopb::ByteString& blob_value() const; - - const Reference& reference_value() const; - - const GeoPoint& geo_point_value() const; - - const Array& array_value() const; - - const Map& object_value() const; - - bool is_null() const { - return type() == Type::Null; - } - - bool is_nan() const { - if (type() != Type::Double) return false; - return std::isnan(double_value()); - } - - /** factory methods. */ - static FieldValue Null(); - static FieldValue True(); - static FieldValue False(); - static FieldValue Nan(); - static FieldValue EmptyObject(); - static FieldValue FromBoolean(bool value); - static FieldValue FromInteger(int64_t value); - static FieldValue FromDouble(double value); - static FieldValue FromTimestamp(const Timestamp& value); - - static FieldValue FromServerTimestamp(const Timestamp& local_write_time); - - private: - // TODO(b/146372592): Make this public once we can use Abseil across - // iOS/public C++ library boundaries. - friend class FieldValueTest; - friend class ServerTimestampTransform; - - static FieldValue FromServerTimestamp( - const Timestamp& local_write_time, - absl::optional previous_value); - - public: - static FieldValue FromString(const char* value); - static FieldValue FromString(const std::string& value); - static FieldValue FromString(std::string&& value); - static FieldValue FromBlob(nanopb::ByteString blob); - static FieldValue FromReference(DatabaseId database_id, DocumentKey key); - static FieldValue FromGeoPoint(const GeoPoint& value); - static FieldValue FromArray(const Array& value); - static FieldValue FromArray(Array&& value); - static FieldValue FromMap(const Map& value); - static FieldValue FromMap(Map&& value); - - size_t Hash() const { - return rep_->Hash(); - } - - util::ComparisonResult CompareTo(const FieldValue& rhs) const { - return rep_->CompareTo(*rhs.rep_); - } - - /** - * Checks if the two values are equal, returning false if the value is - * perceptibly different in any regard. - * - * Comparison for FieldValues is defined by whether or not values should - * match for the purposes of querying. Comparison therefore makes the broadest - * possible allowance, looking only for logical equality. This means that e.g. - * -0.0, +0.0 and 0 (floating point and integer zeros) are all considered the - * same value for comparison purposes. - * - * Equality for FieldValues is defined by whether or not a user could - * perceive a change to the value. That is, a change from integer zero to - * a double zero can be perceived and so these values are unequal despite - * comparing same. - * - * This makes FieldValue one of the special cases where equality is - * inconsistent with comparison. There are cases where CompareTo will return - * Same but operator== will return false. - */ - friend bool operator==(const FieldValue& lhs, const FieldValue& rhs); - - std::string ToString() const { - return rep_->ToString(); - } - - friend std::ostream& operator<<(std::ostream& os, const FieldValue& value); - - friend class ObjectValue; - class BaseValue { - public: - virtual ~BaseValue() = default; - - virtual Type type() const = 0; - - virtual std::string ToString() const = 0; - - virtual bool Equals(const BaseValue& other) const = 0; - - virtual util::ComparisonResult CompareTo(const BaseValue& other) const = 0; - - virtual size_t Hash() const = 0; - - protected: - util::ComparisonResult CompareTypes(const BaseValue& other) const; - }; - - private: - explicit FieldValue(std::shared_ptr rep) : rep_(std::move(rep)) { - } - - std::shared_ptr rep_; -}; - -/** A structured object value stored in Firestore. */ -class ObjectValue : public util::Comparable { - public: - // Default constructible to make using this easy, though prefer - // ObjectValue::Empty() to make intentions clear to readers. - ObjectValue(); - - explicit ObjectValue(FieldValue fv); - - static ObjectValue Empty() { - return ObjectValue(FieldValue::EmptyObject()); - } - - static ObjectValue FromMap(const FieldValue::Map& value); - static ObjectValue FromMap(FieldValue::Map&& value); - - /** - * Returns the value at the given path or absl::nullopt. If the path is empty, - * an identical copy of the FieldValue is returned. - * - * @param field_path the path to search. - * @return The value at the path or absl::nullopt if it doesn't exist. - */ - absl::optional Get(const FieldPath& field_path) const; - - /** - * Returns a FieldValue with the field at the named path set to value. - * Any absent parent of the field will also be created accordingly. - * - * @param field_path The field path to set. Cannot be empty. - * @param value The value to set. - * @return A new FieldValue with the field set. - */ - ObjectValue Set(const FieldPath& field_path, const FieldValue& value) const; - ObjectValue Set(const FieldPath& field_path, const ObjectValue& value) const { - return Set(field_path, value.fv_); - } - - /** - * Returns a FieldValue with the field path deleted. If there is no field at - * the specified path, the returned value is an identical copy. - * - * @param field_path The field path to remove. Cannot be empty. - * @return A new FieldValue with the field path removed. - */ - ObjectValue Delete(const FieldPath& field_path) const; - - /** - * Returns a FieldMask built from all FieldPaths starting from this - * ObjectValue, including paths from nested objects. - */ - FieldMask ToFieldMask() const; - - // TODO(rsgowman): Add Value() method? - // - // Java has a value() method which returns a (non-immutable) java.util.Map, - // which is a copy of the immutable map, but with some fields (such as server - // timestamps) optionally resolved. Do we need the same here? - - const FieldValue::Map& GetInternalValue() const; - - const FieldValue& AsFieldValue() const { - return fv_; - } - - util::ComparisonResult CompareTo(const ObjectValue& rhs) const; - - std::string ToString() const; - friend std::ostream& operator<<(std::ostream& os, const ObjectValue& value); - - size_t Hash() const; - - size_t size() const { - return fv_.object_value().size(); - } - - private: - ObjectValue SetChild(const std::string& child_name, - const FieldValue& value) const; - - FieldValue fv_; -}; - -class FieldValue::Reference { - public: - Reference(DatabaseId database_id, DocumentKey key) - : database_id_(std::move(database_id)), key_(std::move(key)) { - } - - const DatabaseId& database_id() const { - return database_id_; - } - - const DocumentKey& key() const { - return key_; - } - - private: - DatabaseId database_id_; - DocumentKey key_; -}; - -class FieldValue::ServerTimestamp { - private: - // TODO(b/146372592): Make this public once we can use Abseil across - // iOS/public C++ library boundaries. - friend class FieldValue; - - ServerTimestamp(Timestamp local_write_time, - absl::optional previous_value) - : local_write_time_(local_write_time), - previous_value_(std::move(previous_value)) { - } - - public: - const Timestamp& local_write_time() const { - return local_write_time_; - } - - const absl::optional& previous_value() const { - return previous_value_; - } - - private: - Timestamp local_write_time_; - absl::optional previous_value_; -}; - -// Pretend you can automatically upcast from ObjectValue to FieldValue. -inline FieldValue::FieldValue(const ObjectValue& object) - : FieldValue(object.AsFieldValue()) { -} - -inline bool operator!=(const FieldValue& lhs, const FieldValue& rhs) { - // See operator== for why this isn't using util::Same(). - return !(lhs == rhs); -} - -inline bool operator<(const FieldValue& lhs, const FieldValue& rhs) { - return util::Ascending(lhs.CompareTo(rhs)); -} -inline bool operator>(const FieldValue& lhs, const FieldValue& rhs) { - return util::Descending(lhs.CompareTo(rhs)); -} -inline bool operator<=(const FieldValue& lhs, const FieldValue& rhs) { - return !(rhs < lhs); -} -inline bool operator>=(const FieldValue& lhs, const FieldValue& rhs) { - return !(lhs < rhs); -} - -// A bit pattern for our canonical NaN value. Exposed here for testing. -ABSL_CONST_INIT extern const uint64_t kCanonicalNanBits; - -} // namespace model -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_SRC_MODEL_FIELD_VALUE_H_ diff --git a/Firestore/core/src/model/field_value_options.h b/Firestore/core/src/model/field_value_options.h deleted file mode 100644 index dbe5f8c3ca2..00000000000 --- a/Firestore/core/src/model/field_value_options.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2019 Google LLC - * - * 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_MODEL_FIELD_VALUE_OPTIONS_H_ -#define FIRESTORE_CORE_SRC_MODEL_FIELD_VALUE_OPTIONS_H_ - -namespace firebase { -namespace firestore { -namespace model { - -/** Defines the return value for pending server timestamps. */ -enum class ServerTimestampBehavior { kNone, kEstimate, kPrevious }; - -/** Holds properties that define field value deserialization options. */ -class FieldValueOptions { - public: - /** - * Creates an FieldValueOptions instance that specifies deserialization - * behavior for pending server timestamps. - */ - explicit FieldValueOptions(ServerTimestampBehavior server_timestamp_behavior) - : server_timestamp_behavior_(server_timestamp_behavior) { - } - - ServerTimestampBehavior server_timestamp_behavior() const { - return server_timestamp_behavior_; - } - - private: - ServerTimestampBehavior server_timestamp_behavior_; -}; - -} // namespace model -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_SRC_MODEL_FIELD_VALUE_OPTIONS_H_ diff --git a/Firestore/core/src/model/maybe_document.cc b/Firestore/core/src/model/maybe_document.cc deleted file mode 100644 index 8fe4344a95f..00000000000 --- a/Firestore/core/src/model/maybe_document.cc +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2018 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Firestore/core/src/model/maybe_document.h" - -#include -#include - -#include "Firestore/core/src/util/hashing.h" - -namespace firebase { -namespace firestore { -namespace model { - -bool MaybeDocument::Rep::Equals(const MaybeDocument::Rep& other) const { - return type() == other.type() && version() == other.version() && - key() == other.key(); -} - -size_t MaybeDocument::Rep::Hash() const { - return util::Hash(type_, key_, version_); -} - -std::ostream& operator<<(std::ostream& os, const MaybeDocument& doc) { - return os << doc.rep_->ToString(); -} - -bool operator==(const MaybeDocument& lhs, const MaybeDocument& rhs) { - return lhs.rep_ == nullptr - ? rhs.rep_ == nullptr - : (rhs.rep_ != nullptr && lhs.rep_->Equals(*rhs.rep_)); -} - -} // namespace model -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/src/model/maybe_document.h b/Firestore/core/src/model/maybe_document.h deleted file mode 100644 index 4f4cc386a73..00000000000 --- a/Firestore/core/src/model/maybe_document.h +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2018 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FIRESTORE_CORE_SRC_MODEL_MAYBE_DOCUMENT_H_ -#define FIRESTORE_CORE_SRC_MODEL_MAYBE_DOCUMENT_H_ - -#include -#include -#include -#include -#include - -#include "Firestore/core/src/model/document_key.h" -#include "Firestore/core/src/model/snapshot_version.h" - -namespace firebase { -namespace firestore { -namespace model { - -/** - * The result of a lookup for a given path may be an existing document or a - * tombstone that marks the path deleted. - * - * Note: MaybeDocument and its subclasses are specially designed to avoid - * slicing. You can assign a subclass of MaybeDocument to an instance of - * MaybeDocument and the full value is preserved, unsliced. Each subclass - * declares an explicit constructor that can recover the derived type. This - * means that code like this will work: - * - * Document doc(...); - * MaybeDocument maybe_doc = doc; - * Document recovered(maybe_doc); - * - * The final line results in an explicit check that will fail if the type of - * the underlying data is not actually Type::Document. - */ -class MaybeDocument { - public: - /** - * All the different kinds of documents, including MaybeDocument and its - * subclasses. This is used to provide RTTI for documents. See the docstrings - * of the subclasses for details. - */ - enum class Type { - // An unknown subclass of MaybeDocument. This should never happen. - // - // TODO(rsgowman): Since it's no longer possible to directly create - // MaybeDocument's, we can likely remove this value entirely. But - // investigate impact on the serializers first. - Invalid, - - Document, - NoDocument, - UnknownDocument, - }; - - MaybeDocument() = default; - - bool is_valid() const { - return rep_ != nullptr; - } - - /** The runtime type of this document. */ - Type type() const { - return rep_ ? rep_->type() : Type::Invalid; - } - - bool is_document() const { - return type() == Type::Document; - } - - bool is_no_document() const { - return type() == Type::NoDocument; - } - - bool is_unknown_document() const { - return type() == Type::UnknownDocument; - } - - /** The key for this document. */ - const DocumentKey& key() const { - return rep_->key(); - } - - /** - * Returns the version of this document if it exists or a version at which - * this document was guaranteed to not exist. - */ - const SnapshotVersion& version() const { - return rep_->version(); - } - - /** - * Whether this document has a local mutation applied that has not yet been - * acknowledged by Watch. - */ - bool has_pending_writes() const { - return rep_->has_pending_writes(); - } - - size_t Hash() const { - return rep_->Hash(); - } - - std::string ToString() const { - return rep_->ToString(); - } - - friend std::ostream& operator<<(std::ostream& os, const MaybeDocument& doc); - - protected: - class Rep { - public: - Rep(Type type, DocumentKey&& key, SnapshotVersion version) - : type_(type), key_(std::move(key)), version_(version) { - } - - virtual ~Rep() = default; - - Type type() const { - return type_; - } - - const DocumentKey& key() const { - return key_; - } - - const SnapshotVersion& version() const { - return version_; - } - - virtual bool has_pending_writes() const = 0; - - virtual bool Equals(const Rep& other) const; - - virtual size_t Hash() const; - - virtual std::string ToString() const = 0; - - private: - Type type_ = Type::Invalid; - DocumentKey key_; - SnapshotVersion version_; - }; - - explicit MaybeDocument(std::shared_ptr rep) : rep_(std::move(rep)) { - } - - const Rep& rep() const { - return *rep_; - } - - friend bool operator==(const MaybeDocument& lhs, const MaybeDocument& rhs); - - private: - std::shared_ptr rep_; -}; - -inline bool operator!=(const MaybeDocument& lhs, const MaybeDocument& rhs) { - return !(lhs == rhs); -} - -/** Compares against another MaybeDocument by keys only. */ -struct DocumentKeyComparator { - bool operator()(const MaybeDocument& lhs, const MaybeDocument& rhs) const { - return lhs.key() < rhs.key(); - } -}; - -} // namespace model -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_SRC_MODEL_MAYBE_DOCUMENT_H_ diff --git a/Firestore/core/src/model/model_fwd.h b/Firestore/core/src/model/model_fwd.h index 160101edd1e..c59713c237a 100644 --- a/Firestore/core/src/model/model_fwd.h +++ b/Firestore/core/src/model/model_fwd.h @@ -18,8 +18,10 @@ #define FIRESTORE_CORE_SRC_MODEL_MODEL_FWD_H_ #include +#include #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "absl/types/optional.h" namespace firebase { @@ -47,35 +49,38 @@ class SortedSet; } // namespace immutable +namespace nanopb { + +template +class Message; + +} // namespace nanopb + namespace model { class DatabaseId; class DeleteMutation; class Document; +class MutableDocument; class DocumentComparator; class DocumentKey; -class DocumentMap; class DocumentSet; class FieldMask; class FieldPath; class FieldTransform; -class FieldValue; -class MaybeDocument; +class MutableDocument; class Mutation; class MutationBatch; class MutationBatchResult; class MutationResult; -class NoDocument; class ObjectValue; class PatchMutation; class Precondition; class SetMutation; class SnapshotVersion; class TransformOperation; -class UnknownDocument; class VerifyMutation; -enum class DocumentState; enum class OnlineState; struct DocumentKeyHash; @@ -87,20 +92,24 @@ using TargetId = int32_t; using DocumentKeySet = immutable::SortedSet>; -using MaybeDocumentMap = immutable:: - SortedMap>; +using MutableDocumentMap = immutable:: + SortedMap>; -using OptionalMaybeDocumentMap = - immutable::SortedMap, - util::Comparator>; +using DocumentMap = + immutable::SortedMap>; using DocumentVersionMap = std::unordered_map; -using DocumentUpdateMap = std::unordered_map; +using DocumentUpdateMap = + std::unordered_map; + +// A map of FieldPaths to transforms. Sorted so it can be used in +// ObjectValue::SetAll, which makes it more efficient as it processes field +// maps one layer at a time. +using TransformMap = + std::map>>; } // namespace model } // namespace firestore diff --git a/Firestore/core/src/model/mutable_document.cc b/Firestore/core/src/model/mutable_document.cc new file mode 100644 index 00000000000..7b1b905fad8 --- /dev/null +++ b/Firestore/core/src/model/mutable_document.cc @@ -0,0 +1,159 @@ +/* + * Copyright 2021 Google LLC + * + * 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/model/mutable_document.h" + +#include +#include + +#include "Firestore/core/src/model/value_util.h" + +namespace firebase { +namespace firestore { +namespace model { + +MutableDocument MutableDocument::InvalidDocument(DocumentKey document_key) { + return {std::move(document_key), DocumentType::kInvalid, + SnapshotVersion::None(), std::make_shared(), + DocumentState::kSynced}; +} + +MutableDocument MutableDocument::FoundDocument(DocumentKey document_key, + const SnapshotVersion& version, + ObjectValue value) { + return std::move(InvalidDocument(std::move(document_key)) + .ConvertToFoundDocument(version, std::move(value))); +} + +MutableDocument MutableDocument::NoDocument(DocumentKey document_key, + const SnapshotVersion& version) { + return std::move( + InvalidDocument(std::move(document_key)).ConvertToNoDocument(version)); +} + +MutableDocument MutableDocument::UnknownDocument( + const DocumentKey& document_key, const SnapshotVersion& version) { + return std::move( + InvalidDocument(document_key).ConvertToUnknownDocument(version)); +} + +MutableDocument& MutableDocument::ConvertToFoundDocument( + const SnapshotVersion& version, ObjectValue value) { + version_ = version; + document_type_ = DocumentType::kFoundDocument; + value_ = std::make_shared(std::move(value)); + document_state_ = DocumentState::kSynced; + return *this; +} + +MutableDocument& MutableDocument::ConvertToFoundDocument( + const SnapshotVersion& version) { + version_ = version; + document_type_ = DocumentType::kFoundDocument; + document_state_ = DocumentState::kSynced; + return *this; +} + +MutableDocument& MutableDocument::ConvertToNoDocument( + const SnapshotVersion& version) { + version_ = version; + document_type_ = DocumentType::kNoDocument; + value_ = std::make_shared(); + document_state_ = DocumentState::kSynced; + return *this; +} + +MutableDocument& MutableDocument::ConvertToUnknownDocument( + const SnapshotVersion& version) { + version_ = version; + document_type_ = DocumentType::kUnknownDocument; + value_ = std::make_shared(); + document_state_ = DocumentState::kHasCommittedMutations; + return *this; +} + +MutableDocument& MutableDocument::SetHasCommittedMutations() { + document_state_ = DocumentState::kHasCommittedMutations; + return *this; +} + +MutableDocument& MutableDocument::SetHasLocalMutations() { + document_state_ = DocumentState::kHasLocalMutations; + return *this; +} + +MutableDocument MutableDocument::Clone() const { + return MutableDocument( + key_, document_type_, version_, + std::make_shared(DeepClone(value_->Get())), document_state_); +} + +size_t MutableDocument::Hash() const { + return key_.Hash(); +} + +std::string MutableDocument::ToString() const { + std::stringstream stream; + stream << "MutableDocument(key=" << key_ << ", type=" << document_type_ + << ", version=" << version_ << ", value=" << *value_ + << ", state=" << document_state_; + return stream.str(); +} + +bool operator==(const MutableDocument& lhs, const MutableDocument& rhs) { + return lhs.key_ == rhs.key_ && lhs.document_type_ == rhs.document_type_ && + lhs.version_ == rhs.version_ && + lhs.document_state_ == rhs.document_state_ && + *lhs.value_ == *rhs.value_; +} + +std::ostream& operator<<(std::ostream& os, const MutableDocument& doc) { + return os << doc.ToString(); +} + +std::ostream& operator<<(std::ostream& os, + MutableDocument::DocumentState state) { + switch (state) { + case MutableDocument::DocumentState::kHasCommittedMutations: + return os << "kHasCommittedMutations"; + case MutableDocument::DocumentState::kHasLocalMutations: + return os << "kHasLocalMutations"; + case MutableDocument::DocumentState::kSynced: + return os << "kSynced"; + } + + UNREACHABLE(); +} + +std::ostream& operator<<(std::ostream& os, + MutableDocument::DocumentType state) { + switch (state) { + case MutableDocument::DocumentType::kInvalid: + return os << "kInvalid"; + case MutableDocument::DocumentType::kFoundDocument: + return os << "kFoundDocument"; + case MutableDocument::DocumentType::kNoDocument: + return os << "kNoDocument"; + case MutableDocument::DocumentType::kUnknownDocument: + return os << "kUnknownDocument"; + } + + UNREACHABLE(); +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/model/mutable_document.h b/Firestore/core/src/model/mutable_document.h new file mode 100644 index 00000000000..09c1f32f8e2 --- /dev/null +++ b/Firestore/core/src/model/mutable_document.h @@ -0,0 +1,246 @@ +/* + * Copyright 2021 Google LLC + * + * 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_MODEL_MUTABLE_DOCUMENT_H_ +#define FIRESTORE_CORE_SRC_MODEL_MUTABLE_DOCUMENT_H_ + +#include +#include +#include +#include + +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/core/src/model/document_key.h" +#include "Firestore/core/src/model/object_value.h" +#include "Firestore/core/src/model/snapshot_version.h" + +namespace firebase { +namespace firestore { +namespace model { + +/** + * Represents a document in Firestore with a key, version, data and whether it + * has local mutations applied to it. + * + * Documents can transition between states via `ConvertToFoundDocument()`, + * `ConvertToNoDocument()` and `ConvertToUnknownDocument()`. If a document does + * not transition to one of these states even after all mutations have been + * applied, `is_valid_document()` returns false and the document should be + * removed from all views. + */ +class MutableDocument { + private: + enum class DocumentType { + /** + * Represents the initial state of a MutableDocument when only the document + * key is known. Invalid documents transition to other states as mutations + * are applied. If a document remains invalid after applying mutations, it + * should be discarded. + */ + kInvalid, + /** + * Represents a document in Firestore with a key, version, data and whether + * the data has local mutations applied to it. + */ + kFoundDocument, + /** Represents that no documents exists for the key at the given version. */ + kNoDocument, + /** + * Represents an existing document whose data is unknown (e.g. a document + * that was updated without a known base document). + */ + kUnknownDocument + }; + + /** Describes the `hasPendingWrites` state of a document. */ + enum class DocumentState { + /** + * Local mutations applied via the mutation queue. Document is potentially + * inconsistent. + */ + kHasLocalMutations, + /** + * Mutations applied based on a write acknowledgment. Document is + * potentially inconsistent. + */ + kHasCommittedMutations, + /** No mutations applied. Document was sent to us by Watch. */ + kSynced + }; + + public: + MutableDocument() = default; + + /** + * Creates a document with no known version or data. This document can serve + * as a base document for mutations. + */ + static MutableDocument InvalidDocument(DocumentKey document_key); + + /** + * Creates a new document that is known to exist with the given data at the + * given version. + */ + static MutableDocument FoundDocument(DocumentKey document_key, + const SnapshotVersion& version, + ObjectValue value); + + /** Creates a new document that is known to not exisr at the given version. */ + static MutableDocument NoDocument(DocumentKey document_key, + const SnapshotVersion& version); + + /** + * Creates a new document that is known to exist at the given version but + * whose data is not known (e.g. a document that was updated without a known + * base document). + */ + static MutableDocument UnknownDocument(const DocumentKey& document_key, + const SnapshotVersion& version); + + /** + * Changes the document type to indicate that it exists and that its version + * and data are known. + */ + MutableDocument& ConvertToFoundDocument(const SnapshotVersion& version, + ObjectValue value); + + /** + * Changes the document type to indicate that it exists and that its version + * and data are known. + */ + MutableDocument& ConvertToFoundDocument(const SnapshotVersion& version); + + /** + * Changes the document type to indicate that it doesn't exist at the given + * version. + */ + MutableDocument& ConvertToNoDocument(const SnapshotVersion& version); + + /** + * Changes the document type to indicate that it exists at a given version but + * that its data is not known (e.g. a document that was updated without a + * known base document). + */ + MutableDocument& ConvertToUnknownDocument(const SnapshotVersion& version); + + MutableDocument& SetHasCommittedMutations(); + + MutableDocument& SetHasLocalMutations(); + + /** Creates a new document with a copy of the document's data and state. */ + MutableDocument Clone() const; + + const DocumentKey& key() const { + return key_; + } + + const SnapshotVersion& version() const { + return version_; + } + + bool has_local_mutations() const { + return document_state_ == DocumentState::kHasLocalMutations; + } + + bool has_committed_mutations() const { + return document_state_ == DocumentState::kHasCommittedMutations; + } + + bool has_pending_writes() const { + return has_local_mutations() || has_committed_mutations(); + } + + google_firestore_v1_Value value() const { + return value_->Get(); + } + + ObjectValue& data() { + return *value_; + } + + /** + * Returns the value at the given path or absl::nullopt. If the path is empty, + * an identical copy of the FieldValue is returned. + * + * @param field_path the path to search. + * @return The value at the path or absl::nullopt if it doesn't exist. + */ + absl::optional field( + const FieldPath& field_path) const { + return value_->Get(field_path); + } + + bool is_valid_document() const { + return document_type_ != DocumentType ::kInvalid; + } + + bool is_found_document() const { + return document_type_ == DocumentType ::kFoundDocument; + } + + bool is_no_document() const { + return document_type_ == DocumentType ::kNoDocument; + } + + bool is_unknown_document() const { + return document_type_ == DocumentType ::kUnknownDocument; + } + + size_t Hash() const; + + std::string ToString() const; + + friend bool operator==(const MutableDocument& lhs, + const MutableDocument& rhs); + + friend std::ostream& operator<<(std::ostream& os, DocumentState state); + friend std::ostream& operator<<(std::ostream& os, DocumentType type); + + private: + MutableDocument(DocumentKey key, + DocumentType document_type, + SnapshotVersion version, + std::shared_ptr value, + DocumentState document_state) + : key_{std::move(key)}, + document_type_{document_type}, + version_{version}, + value_{std::move(value)}, + document_state_{document_state} { + } + + DocumentKey key_; + DocumentType document_type_ = DocumentType::kInvalid; + SnapshotVersion version_; + // Using a shared pointer to ObjectValue makes MutableDocument copy-assignable + // without having to manually create a deep clone of its Protobuf contents. + std::shared_ptr value_ = std::make_shared(); + DocumentState document_state_ = DocumentState::kSynced; +}; + +bool operator==(const MutableDocument& lhs, const MutableDocument& rhs); + +std::ostream& operator<<(std::ostream& os, const MutableDocument& doc); + +inline bool operator!=(const MutableDocument& lhs, const MutableDocument& rhs) { + return !(lhs == rhs); +} + +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_MODEL_MUTABLE_DOCUMENT_H_ diff --git a/Firestore/core/src/model/mutation.cc b/Firestore/core/src/model/mutation.cc index 9feda0d9ffb..5f8ff96c48b 100644 --- a/Firestore/core/src/model/mutation.cc +++ b/Firestore/core/src/model/mutation.cc @@ -23,8 +23,9 @@ #include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/model/no_document.h" +#include "Firestore/core/src/model/mutable_document.h" +#include "Firestore/core/src/model/object_value.h" +#include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/to_string.h" #include "absl/strings/str_cat.h" @@ -33,6 +34,8 @@ namespace firebase { namespace firestore { namespace model { +using nanopb::Message; + std::string MutationResult::ToString() const { return absl::StrCat( "MutationResult(version=", version_.ToString(), @@ -45,42 +48,32 @@ std::ostream& operator<<(std::ostream& os, const MutationResult& result) { bool operator==(const MutationResult& lhs, const MutationResult& rhs) { return lhs.version() == rhs.version() && - lhs.transform_results() == rhs.transform_results(); + *lhs.transform_results_ == *rhs.transform_results_; } -MaybeDocument Mutation::ApplyToRemoteDocument( - const absl::optional& maybe_doc, - const MutationResult& mutation_result) const { - return rep().ApplyToRemoteDocument(maybe_doc, mutation_result); +void Mutation::ApplyToRemoteDocument( + MutableDocument& document, const MutationResult& mutation_result) const { + return rep().ApplyToRemoteDocument(document, mutation_result); } -absl::optional Mutation::ApplyToLocalView( - const absl::optional& maybe_doc, - const Timestamp& local_write_time) const { - return rep().ApplyToLocalView(maybe_doc, local_write_time); +void Mutation::ApplyToLocalView(MutableDocument& document, + const Timestamp& local_write_time) const { + return rep().ApplyToLocalView(document, local_write_time); } absl::optional Mutation::Rep::ExtractTransformBaseValue( - const absl::optional& maybe_doc) const { + const Document& document) const { absl::optional base_object; - absl::optional document; - if (maybe_doc && maybe_doc->is_document()) { - document = Document(*maybe_doc); - } for (const FieldTransform& transform : field_transforms_) { - absl::optional existing_value; - if (document) { - existing_value = document->field(transform.path()); - } - - absl::optional coerced_value = + auto existing_value = document->field(transform.path()); + auto coerced_value = transform.transformation().ComputeBaseValue(existing_value); if (coerced_value) { if (!base_object) { - base_object = ObjectValue::Empty(); + base_object = ObjectValue{}; } - base_object = base_object->Set(transform.path(), *coerced_value); + base_object->Set(transform.path(), std::move(*coerced_value)); } } @@ -107,79 +100,56 @@ bool Mutation::Rep::Equals(const Mutation::Rep& other) const { field_transforms_ == other.field_transforms_; } -void Mutation::Rep::VerifyKeyMatches( - const absl::optional& maybe_doc) const { - if (maybe_doc) { - HARD_ASSERT(maybe_doc->key() == key(), - "Can only apply a mutation to a document with the same key"); - } +void Mutation::Rep::VerifyKeyMatches(const MutableDocument& document) const { + HARD_ASSERT(document.key() == key(), + "Can only apply a mutation to a document with the same key"); } SnapshotVersion Mutation::Rep::GetPostMutationVersion( - const absl::optional& maybe_doc) { - if (maybe_doc && maybe_doc->type() == MaybeDocument::Type::Document) { - return maybe_doc->version(); + const MutableDocument& document) { + if (document.is_found_document()) { + return document.version(); } else { return SnapshotVersion::None(); } } -std::vector Mutation::Rep::ServerTransformResults( - const absl::optional& maybe_doc, - const std::vector& server_transform_results) const { - HARD_ASSERT(field_transforms_.size() == server_transform_results.size(), - "server transform result size (%s) should match field transforms " - "size (%s)", - server_transform_results.size(), field_transforms_.size()); +TransformMap Mutation::Rep::ServerTransformResults( + const ObjectValue& previous_data, + const Message& server_transform_results) + const { + TransformMap transform_results; + HARD_ASSERT( + field_transforms_.size() == server_transform_results->values_count, + "server transform result size (%s) should match field transforms " + "size (%s)", + server_transform_results->values_count, field_transforms_.size()); - std::vector transform_results; - for (size_t i = 0; i < server_transform_results.size(); i++) { + for (size_t i = 0; i < server_transform_results->values_count; ++i) { const FieldTransform& field_transform = field_transforms_[i]; const TransformOperation& transform = field_transform.transformation(); - - absl::optional previous_value; - if (maybe_doc && maybe_doc->is_document()) { - previous_value = Document(*maybe_doc).field(field_transform.path()); - } - - transform_results.push_back(transform.ApplyToRemoteDocument( - previous_value, server_transform_results[i])); + const auto& previous_value = previous_data.Get(field_transform.path()); + Message transformed_value = + transform.ApplyToRemoteDocument( + previous_value, DeepClone(server_transform_results->values[i])); + transform_results[field_transform.path()] = std::move(transformed_value); } return transform_results; } -std::vector Mutation::Rep::LocalTransformResults( - const absl::optional& maybe_doc, - const Timestamp& local_write_time) const { - std::vector transform_results; +TransformMap Mutation::Rep::LocalTransformResults( + const ObjectValue& previous_data, const Timestamp& local_write_time) const { + TransformMap transform_results; for (const FieldTransform& field_transform : field_transforms_) { const TransformOperation& transform = field_transform.transformation(); - - absl::optional previous_value; - if (maybe_doc && maybe_doc->is_document()) { - previous_value = Document(*maybe_doc).field(field_transform.path()); - } - - transform_results.push_back( - transform.ApplyToLocalView(previous_value, local_write_time)); + const auto& previous_value = previous_data.Get(field_transform.path()); + Message transformed_value = + transform.ApplyToLocalView(previous_value, local_write_time); + transform_results[field_transform.path()] = std::move(transformed_value); } return transform_results; } -ObjectValue Mutation::Rep::TransformObject( - ObjectValue object_value, - const std::vector& transform_results) const { - HARD_ASSERT(transform_results.size() == field_transforms_.size(), - "Transform results size mismatch."); - - for (size_t i = 0; i < field_transforms_.size(); i++) { - const FieldTransform& field_transform = field_transforms_[i]; - const FieldPath& field_path = field_transform.path(); - object_value = object_value.Set(field_path, transform_results[i]); - } - return object_value; -} - bool operator==(const Mutation& lhs, const Mutation& rhs) { return lhs.rep_ == nullptr ? rhs.rep_ == nullptr diff --git a/Firestore/core/src/model/mutation.h b/Firestore/core/src/model/mutation.h index afecdd5dc54..c644f8f875c 100644 --- a/Firestore/core/src/model/mutation.h +++ b/Firestore/core/src/model/mutation.h @@ -18,23 +18,27 @@ #define FIRESTORE_CORE_SRC_MODEL_MUTATION_H_ #include +#include #include #include #include #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/field_transform.h" -#include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/model/object_value.h" #include "Firestore/core/src/model/precondition.h" #include "Firestore/core/src/model/snapshot_version.h" +#include "Firestore/core/src/nanopb/message.h" #include "absl/types/optional.h" namespace firebase { namespace firestore { namespace model { -class MaybeDocument; +class Document; +class MutableDocument; /** * The result of applying a mutation to the server. This is a model of the @@ -46,10 +50,16 @@ class MaybeDocument; */ class MutationResult { public: + /** Takes ownership of `transform_results`. */ MutationResult( SnapshotVersion version, - absl::optional> transform_results) - : version_(version), transform_results_(std::move(transform_results)) { + nanopb::Message transform_results) + : version_(version), transform_results_{std::move(transform_results)} { + } + + MutationResult(MutationResult&& other) noexcept + : version_{other.version_}, + transform_results_{std::move(other.transform_results_)} { } /** @@ -73,7 +83,7 @@ class MutationResult { * * Will be nullopt if the mutation was not a TransformMutation. */ - const absl::optional>& transform_results() + const nanopb::Message& transform_results() const { return transform_results_; } @@ -87,7 +97,7 @@ class MutationResult { private: SnapshotVersion version_; - absl::optional> transform_results_; + nanopb::Message transform_results_; }; /** @@ -175,26 +185,19 @@ class Mutation { } /** - * Applies this mutation to the given MaybeDocument for the purposes of - * computing the committed state of the document after the server has - * acknowledged that this mutation has been successfully committed. This - * means that if the input document doesn't match the expected state (e.g. it - * is `nullopt` or outdated), the local cache must have been incorrect so an - * `UnknownDocument` is returned. + * Applies this mutation to the given Document for the purposes of computing + * the committed state of the document after the server has acknowledged that + * this mutation has been successfully committed. * - * @param maybe_doc The document to mutate. The input document can be - * `nullopt` if the client has no knowledge of the pre-mutation state of - * the document. + * If the input document doesn't match the expected state (e.g. it is invalid + * or outdated), the document state may transition to unknown. + * + * @param document The document to mutate. * @param mutation_result The backend's response of successfully applying the * mutation. - * @return The mutated document. The returned document is not optional - * because the server successfully committed this mutation. If the local - * cache might have caused a `nullopt` result, this method will return an - * `UnknownDocument` instead. */ - MaybeDocument ApplyToRemoteDocument( - const absl::optional& maybe_doc, - const MutationResult& mutation_result) const; + void ApplyToRemoteDocument(MutableDocument& document, + const MutationResult& mutation_result) const; /** * Estimates the latency compensated view of this mutation applied to the @@ -204,33 +207,12 @@ class Mutation { * been committed and so it's possible that the mutation is operating on a * locally non-existent document and may produce a non-existent document. * - * Note: `maybe_doc` and `base_doc` are similar but not the same: - * - * - `base_doc` is the pristine version of the document as it was _before_ - * applying any of the mutations in the batch. This means that for each - * mutation in the batch, `base_doc` stays unchanged; - * - `maybe_doc` is the state of the document _after_ applying all the - * preceding mutations from the batch. In other words, `maybe_doc` is - * passed on from one mutation in the batch to the next, accumulating - * changes. - * - * The only time `maybe_doc` and `base_doc` are guaranteed to be the same is - * for the very first mutation in the batch. The distinction between - * `maybe_doc` and `base_doc` helps ServerTimestampTransform determine the - * "previous" value in a way that makes sense to users. - * - * @param maybe_doc The document to mutate. The input document can be - * `nullopt` if the client has no knowledge of the pre-mutation state of - * the document. + * @param document The document to mutate. * @param local_write_time A timestamp indicating the local write time of the * batch this mutation is a part of. - * @return The mutated document. The returned document may be nullopt, but - * only if maybe_doc was nullopt and the mutation would not create a new - * document. */ - absl::optional ApplyToLocalView( - const absl::optional& maybe_doc, - const Timestamp& local_write_time) const; + void ApplyToLocalView(MutableDocument& document, + const Timestamp& local_write_time) const; /** * If this mutation is not idempotent, returns the base value to persist with @@ -249,8 +231,8 @@ class Mutation { * idempotent mutations. */ absl::optional ExtractTransformBaseValue( - const absl::optional& maybe_doc) const { - return rep_->ExtractTransformBaseValue(maybe_doc); + const Document& document) const { + return rep_->ExtractTransformBaseValue(document); } friend bool operator==(const Mutation& lhs, const Mutation& rhs); @@ -290,50 +272,43 @@ class Mutation { return field_transforms_; } - virtual MaybeDocument ApplyToRemoteDocument( - const absl::optional& maybe_doc, + virtual void ApplyToRemoteDocument( + MutableDocument& document, const MutationResult& mutation_result) const = 0; - virtual absl::optional ApplyToLocalView( - const absl::optional& maybe_doc, - const Timestamp& local_write_time) const = 0; + virtual void ApplyToLocalView(MutableDocument& document, + const Timestamp& local_write_time) const = 0; virtual absl::optional ExtractTransformBaseValue( - const absl::optional&) const; + const Document& document) const; /** - * Creates an array of "transform results" (a transform result is a field + * Creates a map of "transform results" (a transform result is a field * value representing the result of applying a transform) for use after a * mutation containing transforms has been acknowledged by the server. * - * @param maybe_doc The current state of the document after applying all - * previous mutations. + * @param previous_data The state of the data before applying this mutation. * @param server_transform_results The transform results received by the - * server. - * @return The transform results array. + * server. + * @return A map of fields to transform results. */ - virtual std::vector ServerTransformResults( - const absl::optional& maybe_doc, - const std::vector& server_transform_results) const; + TransformMap ServerTransformResults( + const ObjectValue& previous_data, + const nanopb::Message& + server_transform_results) const; /** - * Creates an array of "transform results" (a transform result is a field + * Creates a map of "transform results" (a transform result is a field * value representing the result of applying a transform) for use when * applying a transform locally. * - * @param maybe_doc The current state of the document after applying all - * previous mutations. - * @param local_write_time The local time of the transform (used to - * generate ServerTimestampValues). - * @return The transform results array. + * @param previous_data The state of the data before applying this mutation. + * @param local_write_time The local time of the mutation (used to generate + * ServerTimestampValues). + * @return A map of fields to transform results. */ - virtual std::vector LocalTransformResults( - const absl::optional& maybe_doc, - const Timestamp& local_write_time) const; - - virtual ObjectValue TransformObject( - ObjectValue object_value, - const std::vector& transform_results) const; + TransformMap LocalTransformResults(const ObjectValue& previous_data, + const Timestamp& local_write_time) const; virtual bool Equals(const Rep& other) const; @@ -342,10 +317,10 @@ class Mutation { virtual std::string ToString() const = 0; protected: - void VerifyKeyMatches(const absl::optional& maybe_doc) const; + void VerifyKeyMatches(const MutableDocument& document) const; static SnapshotVersion GetPostMutationVersion( - const absl::optional& maybe_doc); + const MutableDocument& document); private: DocumentKey key_; diff --git a/Firestore/core/src/model/mutation_batch.cc b/Firestore/core/src/model/mutation_batch.cc index 406af46fe96..fbf691f8c55 100644 --- a/Firestore/core/src/model/mutation_batch.cc +++ b/Firestore/core/src/model/mutation_batch.cc @@ -19,8 +19,9 @@ #include #include +#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_key_set.h" -#include "Firestore/core/src/model/maybe_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/mutation_batch_result.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/to_string.h" @@ -40,14 +41,9 @@ MutationBatch::MutationBatch(int batch_id, HARD_ASSERT(!mutations_.empty(), "Cannot create an empty mutation batch"); } -absl::optional MutationBatch::ApplyToRemoteDocument( - absl::optional maybe_doc, - const DocumentKey& document_key, +void MutationBatch::ApplyToRemoteDocument( + MutableDocument& document, const MutationBatchResult& mutation_batch_result) const { - HARD_ASSERT(!maybe_doc || maybe_doc->key() == document_key, - "ApplyTo: key %s doesn't match maybe_doc key %s", - document_key.ToString(), maybe_doc->key().ToString()); - const auto& mutation_results = mutation_batch_result.mutation_results(); HARD_ASSERT(mutation_results.size() == mutations_.size(), "Mismatch between mutations length (%s) and results length (%s)", @@ -55,59 +51,48 @@ absl::optional MutationBatch::ApplyToRemoteDocument( for (size_t i = 0; i < mutations_.size(); i++) { const Mutation& mutation = mutations_[i]; - const MutationResult& mutation_result = mutation_results[i]; - if (mutation.key() == document_key) { - maybe_doc = mutation.ApplyToRemoteDocument(maybe_doc, mutation_result); + if (mutation.key() == document.key()) { + mutation.ApplyToRemoteDocument(document, mutation_results[i]); } } - return maybe_doc; } -absl::optional MutationBatch::ApplyToLocalDocument( - absl::optional maybe_doc, - const DocumentKey& document_key) const { - HARD_ASSERT(!maybe_doc || maybe_doc->key() == document_key, - "key %s doesn't match maybe_doc key %s", document_key.ToString(), - maybe_doc->key().ToString()); - +void MutationBatch::ApplyToLocalDocument(MutableDocument& document) const { // First, apply the base state. This allows us to apply non-idempotent // transform against a consistent set of values. for (const Mutation& mutation : base_mutations_) { - if (mutation.key() == document_key) { - maybe_doc = mutation.ApplyToLocalView(maybe_doc, local_write_time_); + if (mutation.key() == document.key()) { + mutation.ApplyToLocalView(document, local_write_time_); } } - absl::optional base_doc = maybe_doc; - // Second, apply all user-provided mutations. for (const Mutation& mutation : mutations_) { - if (mutation.key() == document_key) { - maybe_doc = mutation.ApplyToLocalView(maybe_doc, local_write_time_); + if (mutation.key() == document.key()) { + mutation.ApplyToLocalView(document, local_write_time_); } } - return maybe_doc; } -MaybeDocumentMap MutationBatch::ApplyToLocalDocumentSet( - const MaybeDocumentMap& document_set) const { +void MutationBatch::ApplyToLocalDocumentSet(DocumentMap& document_map) const { // TODO(mrschmidt): This implementation is O(n^2). If we iterate through the // mutations first (as done in `applyToLocalDocument:documentKey:`), we can // reduce the complexity to O(n). - MaybeDocumentMap mutated_documents = document_set; for (const Mutation& mutation : mutations_) { const DocumentKey& key = mutation.key(); - absl::optional previous_document = - mutated_documents.get(key); - absl::optional mutated_document = - ApplyToLocalDocument(std::move(previous_document), key); - if (mutated_document) { - mutated_documents = mutated_documents.insert(key, *mutated_document); + auto it = document_map.find(key); + HARD_ASSERT(it != document_map.end(), "document for key %s not found", + key.ToString()); + // TODO(mutabledocuments): This method should take a map of MutableDocuments + // and we should remove this cast. + auto& document = const_cast(it->second.get()); + ApplyToLocalDocument(document); + if (!document.is_valid_document()) { + document.ConvertToNoDocument(SnapshotVersion::None()); } } - return mutated_documents; } DocumentKeySet MutationBatch::keys() const { diff --git a/Firestore/core/src/model/mutation_batch.h b/Firestore/core/src/model/mutation_batch.h index e6ff40eecd0..f71879ee241 100644 --- a/Firestore/core/src/model/mutation_batch.h +++ b/Firestore/core/src/model/mutation_batch.h @@ -31,6 +31,8 @@ namespace firebase { namespace firestore { namespace model { +class MutableDocument; + /** * A BatchID that was searched for and not found or a batch ID value known to * be before all known batches. @@ -87,15 +89,12 @@ class MutationBatch { * Applies all the mutations in this MutationBatch to the specified document * to create a new remote document. * - * @param maybe_doc The document to which to apply mutations or nullopt if - * there's no existing document. - * @param document_key The key of the document to apply mutations to. + * @param document The document to which to apply mutations. * @param mutation_batch_result The result of applying the MutationBatch to * the backend. */ - absl::optional ApplyToRemoteDocument( - absl::optional maybe_doc, - const DocumentKey& document_key, + void ApplyToRemoteDocument( + MutableDocument& document, const MutationBatchResult& mutation_batch_result) const; /** @@ -106,20 +105,15 @@ class MutationBatch { * been committed and so it's possible that the mutation is operating on a * locally non-existent document and may produce a non-existent document. * - * @param maybe_doc The document to which to apply mutations or nullopt if - * there's no existing document. - * @param document_key The key of the document to apply mutations to. + * @param document The document to which to apply mutations. */ - absl::optional ApplyToLocalDocument( - absl::optional maybe_doc, - const DocumentKey& document_key) const; + void ApplyToLocalDocument(MutableDocument& document) const; /** * Computes the local view for all provided documents given the mutations in * this batch. */ - MaybeDocumentMap ApplyToLocalDocumentSet( - const MaybeDocumentMap& document_set) const; + void ApplyToLocalDocumentSet(DocumentMap& document_map) const; /** * Returns the set of unique keys referenced by all mutations in the batch. diff --git a/Firestore/core/src/model/no_document.cc b/Firestore/core/src/model/no_document.cc deleted file mode 100644 index b331ec5fb7f..00000000000 --- a/Firestore/core/src/model/no_document.cc +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2018 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Firestore/core/src/model/no_document.h" - -#include -#include -#include - -#include "Firestore/core/src/util/hard_assert.h" -#include "Firestore/core/src/util/hashing.h" -#include "absl/strings/str_cat.h" - -namespace firebase { -namespace firestore { -namespace model { - -static_assert( - sizeof(MaybeDocument) == sizeof(NoDocument), - "NoDocument may not have additional members (everything goes in Rep)"); - -class NoDocument::Rep : public MaybeDocument::Rep { - public: - Rep(DocumentKey key, SnapshotVersion version, bool has_committed_mutations) - : MaybeDocument::Rep(Type::NoDocument, std::move(key), version), - has_committed_mutations_(has_committed_mutations) { - } - - bool has_pending_writes() const override { - return has_committed_mutations_; - } - - bool Equals(const MaybeDocument::Rep& other) const override { - if (!MaybeDocument::Rep::Equals(other)) return false; - - const auto& other_rep = static_cast(other); - return has_committed_mutations_ == other_rep.has_committed_mutations_; - } - - size_t Hash() const override { - return util::Hash(MaybeDocument::Rep::Hash(), has_committed_mutations_); - } - - std::string ToString() const override { - return absl::StrCat( - "NoDocument(key=", key().ToString(), ", version=", version().ToString(), - ", has_committed_mutations=", has_committed_mutations_, ")"); - } - - private: - friend class NoDocument; - - bool has_committed_mutations_ = false; -}; - -NoDocument::NoDocument(DocumentKey key, - SnapshotVersion version, - bool has_committed_mutations) - : MaybeDocument(std::make_shared( - std::move(key), version, has_committed_mutations)) { -} - -NoDocument::NoDocument(const MaybeDocument& document) - : MaybeDocument(document) { - HARD_ASSERT(type() == Type::NoDocument); -} - -bool NoDocument::has_committed_mutations() const { - return doc_rep().has_committed_mutations_; -} - -const NoDocument::Rep& NoDocument::doc_rep() const { - return static_cast(MaybeDocument::rep()); -} - -} // namespace model -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/src/model/no_document.h b/Firestore/core/src/model/no_document.h deleted file mode 100644 index 49d767f5b45..00000000000 --- a/Firestore/core/src/model/no_document.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2018 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FIRESTORE_CORE_SRC_MODEL_NO_DOCUMENT_H_ -#define FIRESTORE_CORE_SRC_MODEL_NO_DOCUMENT_H_ - -#include "Firestore/core/src/model/maybe_document.h" - -namespace firebase { -namespace firestore { -namespace model { - -/** Represents that no documents exists for the key at the given version. */ -class NoDocument : public MaybeDocument { - public: - NoDocument(DocumentKey key, - SnapshotVersion version, - bool has_committed_mutations); - - /** - * Casts a MaybeDocument to a NoDocument. This is a checked operation that - * will assert if the type of the MaybeDocument isn't actually - * Type::NoDocument. - */ - explicit NoDocument(const MaybeDocument& document); - - /** Creates an invalid NoDocument instance. */ - NoDocument() = default; - - bool has_committed_mutations() const; - - private: - class Rep; - - const Rep& doc_rep() const; -}; - -} // namespace model -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_SRC_MODEL_NO_DOCUMENT_H_ diff --git a/Firestore/core/src/model/object_value.cc b/Firestore/core/src/model/object_value.cc new file mode 100644 index 00000000000..479e0ee87e4 --- /dev/null +++ b/Firestore/core/src/model/object_value.cc @@ -0,0 +1,383 @@ +/* + * Copyright 2021 Google LLC + * + * 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/model/object_value.h" + +#include +#include +#include + +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/core/src/nanopb/fields_array.h" +#include "Firestore/core/src/nanopb/message.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" +#include "Firestore/core/src/util/hashing.h" +#include "absl/types/span.h" + +namespace firebase { +namespace firestore { +namespace model { + +namespace { + +using nanopb::CheckedSize; +using nanopb::FreeFieldsArray; +using nanopb::FreeNanopbMessage; +using nanopb::MakeArray; +using nanopb::MakeBytesArray; +using nanopb::MakeString; +using nanopb::MakeStringView; +using nanopb::Message; +using nanopb::ReleaseFieldOwnership; +using nanopb::SetRepeatedField; + +struct MapEntryKeyCompare { + bool operator()(const google_firestore_v1_MapValue_FieldsEntry& entry, + absl::string_view segment) const { + return nanopb::MakeStringView(entry.key) < segment; + } + bool operator()(absl::string_view segment, + const google_firestore_v1_MapValue_FieldsEntry& entry) const { + return segment < nanopb::MakeStringView(entry.key); + } +}; + +/** + * Finds an entry by key in the provided map value. Returns `nullptr` if the + * entry does not exist. + */ +google_firestore_v1_MapValue_FieldsEntry* FindEntry( + const google_firestore_v1_Value& value, absl::string_view segment) { + if (!IsMap(value)) { + return nullptr; + } + const google_firestore_v1_MapValue& map_value = value.map_value; + + // MapValues in iOS are always stored in sorted order. + auto found = std::equal_range(map_value.fields, + map_value.fields + map_value.fields_count, + segment, MapEntryKeyCompare()); + + if (found.first == found.second) { + return nullptr; + } + + return found.first; +} + +size_t CalculateSizeOfUnion( + const google_firestore_v1_MapValue& map_value, + const std::map>& upserts, + const std::set& deletes) { + // Compute the size of the map after applying all mutations. The final size is + // the number of existing entries, plus the number of new entries + // minus the number of deleted entries. + return upserts.size() + + std::count_if( + map_value.fields, map_value.fields + map_value.fields_count, + [&](const google_firestore_v1_MapValue_FieldsEntry& entry) { + std::string field = MakeString(entry.key); + // Don't count if entry is deleted or if it is a replacement + // rather than an insert. + return deletes.find(field) == deletes.end() && + upserts.find(field) == upserts.end(); + }); +} + +/** + * Modifies `parent_map` by adding, replacing or deleting the specified + * entries. + */ +void ApplyChanges( + google_firestore_v1_MapValue* parent, + std::map> upserts, + std::set deletes) { + // TODO(mrschmidt): Consider using `absl::btree_map` and `absl::btree_set` for + // potentially better performance. + auto source_count = parent->fields_count; + auto* source_fields = parent->fields; + + size_t target_count = CalculateSizeOfUnion(*parent, upserts, deletes); + auto* target_fields = MakeArray( + CheckedSize(target_count)); + + auto delete_it = deletes.begin(); + auto upsert_it = upserts.begin(); + + // Merge the existing data with the deletes and updates + pb_size_t source_index = 0, target_index = 0; + while (target_index < target_count) { + auto& target_entry = target_fields[target_index]; + + if (source_index < source_count) { + auto& source_entry = source_fields[source_index]; + std::string source_key = MakeString(source_entry.key); + + // Check if the source key is deleted + if (delete_it != deletes.end() && *delete_it == source_key) { + FreeFieldsArray(&source_entry); + + ++delete_it; + ++source_index; + continue; + } + + // Check if the source key is updated by the next upsert + if (upsert_it != upserts.end() && upsert_it->first == source_key) { + FreeFieldsArray(&source_entry.value); + + target_entry.key = source_entry.key; + target_entry.value = *(upsert_it->second.release()); + SortFields(target_entry.value); + + ++upsert_it; + ++source_index; + ++target_index; + continue; + } + + // Check if the source key comes before the next upsert + if (upsert_it == upserts.end() || upsert_it->first > source_key) { + target_entry = source_entry; + + ++source_index; + ++target_index; + continue; + } + } + + // Otherwise, insert the next upsert. + target_entry.key = MakeBytesArray(upsert_it->first); + target_entry.value = *(upsert_it->second.release()); + SortFields(target_entry.value); + + ++upsert_it; + ++target_index; + } + + // Delete any remaining fields in the original map. This only includes fields + // that were deleted. + for (; source_index < source_count; ++source_index) { + FreeFieldsArray(&source_fields[source_index]); + } + + free(parent->fields); + parent->fields = target_fields; + parent->fields_count = CheckedSize(target_count); +} + +} // namespace + +ObjectValue::ObjectValue() { + value_->which_value_type = google_firestore_v1_Value_map_value_tag; + value_->map_value = {}; +} + +ObjectValue::ObjectValue(Message value) + : value_(std::move(value)) { + HARD_ASSERT(value_ && IsMap(*value_), + "ObjectValues should be backed by a MapValue"); + SortFields(*value_); +} + +ObjectValue::ObjectValue(const ObjectValue& other) + : value_(DeepClone(*other.value_)) { +} + +ObjectValue ObjectValue::FromMapValue( + Message map_value) { + Message value; + value->which_value_type = google_firestore_v1_Value_map_value_tag; + value->map_value = *map_value.release(); + return ObjectValue{std::move(value)}; +} + +ObjectValue ObjectValue::FromFieldsEntry( + google_firestore_v1_Document_FieldsEntry* fields_entry, pb_size_t count) { + Message value; + value->which_value_type = google_firestore_v1_Value_map_value_tag; + SetRepeatedField( + &value->map_value.fields, &value->map_value.fields_count, + absl::Span(fields_entry, count), + [](const google_firestore_v1_Document_FieldsEntry& entry) { + return google_firestore_v1_MapValue_FieldsEntry{entry.key, entry.value}; + }); + // Prevent double-freeing of the document's fields. The fields are now owned + // by ObjectValue. + ReleaseFieldOwnership(fields_entry, count); + return ObjectValue{std::move(value)}; +} + +FieldMask ObjectValue::ToFieldMask() const { + return ExtractFieldMask(value_->map_value); +} + +FieldMask ObjectValue::ExtractFieldMask( + const google_firestore_v1_MapValue& value) const { + std::set fields; + + for (size_t i = 0; i < value.fields_count; ++i) { + const google_firestore_v1_MapValue_FieldsEntry& entry = value.fields[i]; + FieldPath current_path{MakeString(entry.key)}; + + if (!IsMap(entry.value)) { + fields.insert(std::move(current_path)); + continue; + } + + // Recursively extract the nested map + FieldMask nested_mask = ExtractFieldMask(entry.value.map_value); + if (nested_mask.begin() == nested_mask.end()) { + // Preserve the empty map by adding it to the FieldMask + fields.insert(std::move(current_path)); + } else { + for (const FieldPath& nested_path : nested_mask) { + fields.insert(current_path.Append(nested_path)); + } + } + } + + return FieldMask(std::move(fields)); +} + +absl::optional ObjectValue::Get( + const FieldPath& path) const { + if (path.empty()) { + return *value_; + } + + google_firestore_v1_Value nested_value = *value_; + for (const std::string& segment : path) { + google_firestore_v1_MapValue_FieldsEntry* entry = + FindEntry(nested_value, segment); + if (!entry) return absl::nullopt; + nested_value = entry->value; + } + return nested_value; +} + +google_firestore_v1_Value ObjectValue::Get() const { + return *value_; +} + +void ObjectValue::Set(const FieldPath& path, + Message value) { + HARD_ASSERT(!path.empty(), "Cannot set field for empty path on ObjectValue"); + + google_firestore_v1_MapValue* parent_map = ParentMap(path.PopLast()); + + std::map> upserts; + upserts[path.last_segment()] = std::move(value); + + ApplyChanges(parent_map, std::move(upserts), /*deletes=*/{}); +} + +void ObjectValue::SetAll(TransformMap data) { + FieldPath parent; + + std::map> upserts; + std::set deletes; + + for (auto& it : data) { + const FieldPath& path = it.first; + absl::optional> value = + std::move(it.second); + + if (!parent.IsImmediateParentOf(path)) { + // Insert the accumulated changes at this parent location + google_firestore_v1_MapValue* parent_map = ParentMap(parent); + ApplyChanges(parent_map, std::move(upserts), std::move(deletes)); + upserts.clear(); + deletes.clear(); + parent = path.PopLast(); + } + + if (value) { + upserts[path.last_segment()] = std::move(*value); + } else { + deletes.insert(path.last_segment()); + } + } + + google_firestore_v1_MapValue* parent_map = ParentMap(parent); + ApplyChanges(parent_map, std::move(upserts), std::move(deletes)); +} + +void ObjectValue::Delete(const FieldPath& path) { + HARD_ASSERT(!path.empty(), "Cannot delete field with empty path"); + + google_firestore_v1_Value* nested_value = value_.get(); + for (const std::string& segment : path.PopLast()) { + auto* entry = FindEntry(*nested_value, segment); + // If the entry is not found, exit early. There is nothing to delete. + if (!entry) return; + nested_value = &entry->value; + } + + // We can only delete a leaf entry if its parent is a map. + if (IsMap(*nested_value)) { + std::set deletes{path.last_segment()}; + ApplyChanges(&nested_value->map_value, /*upserts=*/{}, deletes); + } +} + +std::string ObjectValue::ToString() const { + return CanonicalId(*value_); +} + +size_t ObjectValue::Hash() const { + return util::Hash(CanonicalId(*value_)); +} + +google_firestore_v1_MapValue* ObjectValue::ParentMap(const FieldPath& path) { + google_firestore_v1_Value* parent = value_.get(); + + // Find a or create a parent map entry for `path`. + for (const std::string& segment : path) { + google_firestore_v1_MapValue_FieldsEntry* entry = + FindEntry(*parent, segment); + + if (entry) { + if (entry->value.which_value_type != + google_firestore_v1_Value_map_value_tag) { + // Since the element is not a map value, free all existing data and + // change it to a map type. + FreeFieldsArray(&entry->value); + entry->value.which_value_type = google_firestore_v1_Value_map_value_tag; + entry->value.map_value = {}; + } + + parent = &entry->value; + } else { + // Create a new map value for the current segment. + Message new_entry; + new_entry->which_value_type = google_firestore_v1_Value_map_value_tag; + new_entry->map_value = {}; + + std::map> upserts; + upserts[segment] = std::move(new_entry); + ApplyChanges(&parent->map_value, std::move(upserts), /*deletes=*/{}); + + parent = &(FindEntry(*parent, segment)->value); + } + } + + return &parent->map_value; +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/model/object_value.h b/Firestore/core/src/model/object_value.h new file mode 100644 index 00000000000..45f505a0b32 --- /dev/null +++ b/Firestore/core/src/model/object_value.h @@ -0,0 +1,151 @@ +/* + * Copyright 2021 Google LLC + * + * 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_MODEL_OBJECT_VALUE_H_ +#define FIRESTORE_CORE_SRC_MODEL_OBJECT_VALUE_H_ + +#include +#include +#include +#include +#include + +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/core/src/model/field_mask.h" +#include "Firestore/core/src/model/field_path.h" +#include "Firestore/core/src/model/model_fwd.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/nanopb/message.h" +#include "Firestore/core/src/util/hard_assert.h" +#include "absl/types/optional.h" + +namespace firebase { +namespace firestore { + +namespace model { + +/** A structured object value stored in Firestore. */ +class ObjectValue { + public: + ObjectValue(); + + /** Creates a new ObjectValue */ + explicit ObjectValue(nanopb::Message value); + + ObjectValue(ObjectValue&& other) noexcept = default; + ObjectValue& operator=(ObjectValue&& other) noexcept = default; + ObjectValue(const ObjectValue& other); + + ObjectValue& operator=(const ObjectValue&) = delete; + + /** + * Creates a new ObjectValue that is backed by the given `map_value`. + * ObjectValue takes on ownership of the data. + */ + static ObjectValue FromMapValue( + nanopb::Message map_value); + + /** + * Creates a new ObjectValue that is backed by the provided document fields. + * ObjectValue takes on ownership of the data and zeroes out the pointers in + * `fields_entry`. This allows the callsite to destruct the Document proto + * without affecting the fields data. + */ + static ObjectValue FromFieldsEntry( + google_firestore_v1_Document_FieldsEntry* fields_entry, pb_size_t count); + + /** Recursively extracts the FieldPaths that are set in this ObjectValue. */ + FieldMask ToFieldMask() const; + + /** + * Returns the value at the given path or null. + * + * @param path the path to search + * @return The value at the path or null if it doesn't exist. + */ + absl::optional Get(const FieldPath& path) const; + + /** + * Returns the ObjectValue in its Protobuf representation. + */ + google_firestore_v1_Value Get() const; + + /** + * Sets the field to the provided value. + * + * @param path The field path to set. The path must not be empty. + * @param value The value to set. + */ + void Set(const FieldPath& path, + nanopb::Message value); + + /** + * Sets the provided fields to the provided values. Fields set to `nullopt` + * are deleted. + * + * Takes ownership of data. + * + * @param data A map of fields to values (or nullopt for deletes) + */ + void SetAll(TransformMap data); + + /** + * Removes the field at the specified path. If there is no field at the + * specified path, nothing is changed. + * + * @param path The field path to remove. The path must not be empty. + */ + void Delete(const FieldPath& path); + + std::string ToString() const; + + size_t Hash() const; + + friend bool operator==(const ObjectValue& lhs, const ObjectValue& rhs); + friend std::ostream& operator<<(std::ostream& out, + const ObjectValue& object_value); + + private: + /** Returns the field mask for the provided map value. */ + FieldMask ExtractFieldMask(const google_firestore_v1_MapValue& value) const; + + /** + * Returns the map that contains the leaf element of `path`. If the parent + * entry does not yet exist, or if it is not a map, a new map will be created. + */ + google_firestore_v1_MapValue* ParentMap(const FieldPath& path); + + nanopb::Message value_; +}; + +inline bool operator==(const ObjectValue& lhs, const ObjectValue& rhs) { + return *lhs.value_ == *rhs.value_; +} + +inline bool operator!=(const ObjectValue& lhs, const ObjectValue& rhs) { + return !(lhs == rhs); +} + +inline std::ostream& operator<<(std::ostream& out, + const ObjectValue& object_value) { + return out << "ObjectValue(" << *object_value.value_ << ")"; +} + +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_MODEL_OBJECT_VALUE_H_ diff --git a/Firestore/core/src/model/patch_mutation.cc b/Firestore/core/src/model/patch_mutation.cc index 209551a288f..537367ca7d9 100644 --- a/Firestore/core/src/model/patch_mutation.cc +++ b/Firestore/core/src/model/patch_mutation.cc @@ -19,11 +19,8 @@ #include #include -#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/model/no_document.h" -#include "Firestore/core/src/model/unknown_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/to_string.h" @@ -31,6 +28,8 @@ namespace firebase { namespace firestore { namespace model { +using nanopb::Message; + static_assert( sizeof(Mutation) == sizeof(PatchMutation), "PatchMutation may not have additional members (everything goes in Rep)"); @@ -73,75 +72,57 @@ PatchMutation::Rep::Rep(DocumentKey&& key, mask_(std::move(mask)) { } -MaybeDocument PatchMutation::Rep::ApplyToRemoteDocument( - const absl::optional& maybe_doc, - const MutationResult& mutation_result) const { - VerifyKeyMatches(maybe_doc); +void PatchMutation::Rep::ApplyToRemoteDocument( + MutableDocument& document, const MutationResult& mutation_result) const { + VerifyKeyMatches(document); - if (!precondition().IsValidFor(maybe_doc)) { + if (!precondition().IsValidFor(document)) { // Since the mutation was not rejected, we know that the precondition // matched on the backend. We therefore must not have the expected version // of the document in our cache and return an UnknownDocument with the known // update_time. - return UnknownDocument(key(), mutation_result.version()); + document.ConvertToUnknownDocument(mutation_result.version()); + return; } - std::vector transform_results = - mutation_result.transform_results() != absl::nullopt - ? ServerTransformResults(maybe_doc, - *mutation_result.transform_results()) - : std::vector(); - - ObjectValue new_data = PatchDocument(maybe_doc, transform_results); - const SnapshotVersion& version = mutation_result.version(); - return Document(std::move(new_data), key(), version, - DocumentState::kCommittedMutations); + ObjectValue& data = document.data(); + auto transform_results = + ServerTransformResults(data, mutation_result.transform_results()); + data.SetAll(GetPatch()); + data.SetAll(std::move(transform_results)); + document.ConvertToFoundDocument(mutation_result.version()) + .SetHasCommittedMutations(); } -absl::optional PatchMutation::Rep::ApplyToLocalView( - const absl::optional& maybe_doc, - const Timestamp& local_write_time) const { - VerifyKeyMatches(maybe_doc); +void PatchMutation::Rep::ApplyToLocalView( + MutableDocument& document, const Timestamp& local_write_time) const { + VerifyKeyMatches(document); - if (!precondition().IsValidFor(maybe_doc)) { - return maybe_doc; + if (!precondition().IsValidFor(document)) { + return; } - std::vector transform_results = - LocalTransformResults(maybe_doc, local_write_time); - - ObjectValue new_data = PatchDocument(maybe_doc, transform_results); - SnapshotVersion version = GetPostMutationVersion(maybe_doc); - return Document(std::move(new_data), key(), version, - DocumentState::kLocalMutations); -} - -ObjectValue PatchMutation::Rep::PatchDocument( - const absl::optional& maybe_doc, - const std::vector& transform_results) const { - ObjectValue data; - if (maybe_doc && maybe_doc->type() == MaybeDocument::Type::Document) { - data = Document(*maybe_doc).data(); - } else { - data = ObjectValue::Empty(); - } - data = PatchObject(data); - data = TransformObject(data, transform_results); - return data; + ObjectValue& data = document.data(); + auto transform_results = LocalTransformResults(data, local_write_time); + data.SetAll(GetPatch()); + data.SetAll(std::move(transform_results)); + document.ConvertToFoundDocument(GetPostMutationVersion(document)) + .SetHasLocalMutations(); } -ObjectValue PatchMutation::Rep::PatchObject(ObjectValue obj) const { +TransformMap PatchMutation::Rep::GetPatch() const { + TransformMap result; for (const FieldPath& path : mask_) { if (!path.empty()) { - absl::optional new_value = value_.Get(path); - if (!new_value) { - obj = obj.Delete(path); + auto value = value_.Get(path); + if (value) { + result[path] = DeepClone(*value); } else { - obj = obj.Set(path, *new_value); + result[path] = absl::nullopt; } } } - return obj; + return result; } bool PatchMutation::Rep::Equals(const Mutation::Rep& other) const { diff --git a/Firestore/core/src/model/patch_mutation.h b/Firestore/core/src/model/patch_mutation.h index 250aa3a80c6..4b34cd2ac5a 100644 --- a/Firestore/core/src/model/patch_mutation.h +++ b/Firestore/core/src/model/patch_mutation.h @@ -17,13 +17,13 @@ #ifndef FIRESTORE_CORE_SRC_MODEL_PATCH_MUTATION_H_ #define FIRESTORE_CORE_SRC_MODEL_PATCH_MUTATION_H_ +#include #include #include #include #include #include "Firestore/core/src/model/field_mask.h" -#include "Firestore/core/src/model/field_value.h" #include "Firestore/core/src/model/model_fwd.h" #include "Firestore/core/src/model/mutation.h" @@ -102,13 +102,18 @@ class PatchMutation : public Mutation { return mask_; } - MaybeDocument ApplyToRemoteDocument( - const absl::optional& maybe_doc, + /** + * Returns this patch mutation as a list of field paths to values (or + * nullopt for deletes). + */ + TransformMap GetPatch() const; + + void ApplyToRemoteDocument( + MutableDocument& document, const MutationResult& mutation_result) const override; - absl::optional ApplyToLocalView( - const absl::optional& maybe_doc, - const Timestamp& local_write_time) const override; + void ApplyToLocalView(MutableDocument& document, + const Timestamp& local_write_time) const override; bool Equals(const Mutation::Rep& other) const override; @@ -117,12 +122,6 @@ class PatchMutation : public Mutation { std::string ToString() const override; private: - ObjectValue PatchDocument( - const absl::optional& maybe_doc, - const std::vector& transform_results) const; - - ObjectValue PatchObject(ObjectValue obj) const; - ObjectValue value_; FieldMask mask_; }; diff --git a/Firestore/core/src/model/precondition.cc b/Firestore/core/src/model/precondition.cc index 4822cb0cb55..cacf7889c39 100644 --- a/Firestore/core/src/model/precondition.cc +++ b/Firestore/core/src/model/precondition.cc @@ -16,7 +16,7 @@ #include "Firestore/core/src/model/precondition.h" -#include "Firestore/core/src/model/maybe_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/snapshot_version.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/hashing.h" @@ -47,15 +47,12 @@ Precondition Precondition::None() { return Precondition{Type::None, SnapshotVersion::None(), false}; } -bool Precondition::IsValidFor( - const absl::optional& maybe_doc) const { +bool Precondition::IsValidFor(const MutableDocument& document) const { switch (type_) { case Type::UpdateTime: - return maybe_doc && maybe_doc->type() == MaybeDocument::Type::Document && - maybe_doc->version() == update_time_; + return document.is_found_document() && document.version() == update_time_; case Type::Exists: - return (exists_ == (maybe_doc && - maybe_doc->type() == MaybeDocument::Type::Document)); + return exists_ == document.is_found_document(); case Type::None: return true; } diff --git a/Firestore/core/src/model/precondition.h b/Firestore/core/src/model/precondition.h index 5f42ca67fe6..0f58a89d091 100644 --- a/Firestore/core/src/model/precondition.h +++ b/Firestore/core/src/model/precondition.h @@ -27,7 +27,7 @@ namespace firebase { namespace firestore { namespace model { -class MaybeDocument; +class MutableDocument; /** * Encodes a precondition for a mutation. This follows the model that the @@ -61,7 +61,7 @@ class Precondition { * Returns true if the precondition is valid for the given document (and the * document is available). */ - bool IsValidFor(const absl::optional& maybe_doc) const; + bool IsValidFor(const MutableDocument& document) const; Type type() const { return type_; diff --git a/Firestore/core/src/model/server_timestamp_util.cc b/Firestore/core/src/model/server_timestamp_util.cc new file mode 100644 index 00000000000..d723fcb28b3 --- /dev/null +++ b/Firestore/core/src/model/server_timestamp_util.cc @@ -0,0 +1,123 @@ +/* + * Copyright 2021 Google LLC + * + * 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/model/server_timestamp_util.h" + +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" +#include "Firestore/core/src/util/hard_assert.h" +#include "absl/strings/string_view.h" + +namespace firebase { +namespace firestore { +namespace model { + +using nanopb::Message; + +const char kTypeKey[] = "__type__"; +const char kLocalWriteTimeKey[] = "__local_write_time__"; +const char kPreviousValueKey[] = "__previous_value__"; +const char kServerTimestampSentinel[] = "server_timestamp"; + +Message EncodeServerTimestamp( + const Timestamp& local_write_time, + absl::optional previous_value) { + pb_size_t count = previous_value ? 3 : 2; + + Message result; + result->which_value_type = google_firestore_v1_Value_map_value_tag; + result->map_value.fields_count = count; + result->map_value.fields = + nanopb::MakeArray(count); + + auto* field = result->map_value.fields; + field->key = nanopb::MakeBytesArray(kTypeKey); + field->value.which_value_type = google_firestore_v1_Value_string_value_tag; + field->value.string_value = nanopb::MakeBytesArray(kServerTimestampSentinel); + + ++field; + field->key = nanopb::MakeBytesArray(kLocalWriteTimeKey); + field->value.which_value_type = google_firestore_v1_Value_timestamp_value_tag; + field->value.timestamp_value.seconds = local_write_time.seconds(); + field->value.timestamp_value.nanos = local_write_time.nanoseconds(); + + if (previous_value) { + ++field; + field->key = nanopb::MakeBytesArray(kPreviousValueKey); + field->value = *DeepClone(*previous_value).release(); + } + + return result; +} + +bool IsServerTimestamp(const google_firestore_v1_Value& value) { + if (!IsMap(value)) { + return false; + } + + if (value.map_value.fields_count > 3) { + return false; + } + + for (size_t i = 0; i < value.map_value.fields_count; ++i) { + const auto& field = value.map_value.fields[i]; + absl::string_view key = nanopb::MakeStringView(field.key); + if (key == kTypeKey) { + return field.value.which_value_type == + google_firestore_v1_Value_string_value_tag && + nanopb::MakeStringView(field.value.string_value) == + kServerTimestampSentinel; + } + } + + return false; +} + +google_protobuf_Timestamp GetLocalWriteTime( + const firebase::firestore::google_firestore_v1_Value& value) { + for (size_t i = 0; i < value.map_value.fields_count; ++i) { + const auto& field = value.map_value.fields[i]; + absl::string_view key = nanopb::MakeStringView(field.key); + if (key == kLocalWriteTimeKey && + field.value.which_value_type == + google_firestore_v1_Value_timestamp_value_tag) { + return field.value.timestamp_value; + } + } + + HARD_FAIL("LocalWriteTime not found"); +} + +absl::optional GetPreviousValue( + const google_firestore_v1_Value& value) { + for (size_t i = 0; i < value.map_value.fields_count; ++i) { + const auto& field = value.map_value.fields[i]; + absl::string_view key = nanopb::MakeStringView(field.key); + if (key == kPreviousValueKey) { + if (IsServerTimestamp(field.value)) { + return GetPreviousValue(field.value); + } else { + return field.value; + } + } + } + + return absl::nullopt; +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/model/server_timestamp_util.h b/Firestore/core/src/model/server_timestamp_util.h new file mode 100644 index 00000000000..6fe80737d79 --- /dev/null +++ b/Firestore/core/src/model/server_timestamp_util.h @@ -0,0 +1,61 @@ +/* + * Copyright 2021 Google LLC + * + * 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_MODEL_SERVER_TIMESTAMP_UTIL_H_ +#define FIRESTORE_CORE_SRC_MODEL_SERVER_TIMESTAMP_UTIL_H_ + +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/core/include/firebase/firestore/timestamp.h" +#include "Firestore/core/src/nanopb/message.h" +#include "absl/types/optional.h" + +namespace firebase { +namespace firestore { +namespace model { + +// Utility methods to handle ServerTimestamps, which are stored using special +// sentinel fields in MapValues. + +/** Encodes the backing data for a server timestamp in a Value proto. */ +nanopb::Message EncodeServerTimestamp( + const Timestamp& local_write_time, + absl::optional previous_value); +/** + * Returns whether the provided value is a field map that contains the + * sentinel values of a ServerTimestamp. + */ +bool IsServerTimestamp(const google_firestore_v1_Value& value); + +/** + * Returns the local time at which the timestamp was written to the document. + */ +google_protobuf_Timestamp GetLocalWriteTime( + const google_firestore_v1_Value& value); + +/** + * Returns the value of the field before this ServerTimestamp was set. + * + * Preserving the previous values allows the user to display the last resolved + * value until the backend responds with the timestamp. + */ +absl::optional GetPreviousValue( + const google_firestore_v1_Value& value); + +} // namespace model +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_MODEL_SERVER_TIMESTAMP_UTIL_H_ diff --git a/Firestore/core/src/model/set_mutation.cc b/Firestore/core/src/model/set_mutation.cc index 855c92e8a99..ba54681b48c 100644 --- a/Firestore/core/src/model/set_mutation.cc +++ b/Firestore/core/src/model/set_mutation.cc @@ -19,9 +19,8 @@ #include #include -#include "Firestore/core/src/model/document.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/model/no_document.h" +#include "Firestore/core/src/model/mutable_document.h" +#include "Firestore/core/src/model/value_util.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/hashing.h" #include "Firestore/core/src/util/to_string.h" @@ -67,40 +66,37 @@ SetMutation::Rep::Rep(DocumentKey&& key, value_(std::move(value)) { } -MaybeDocument SetMutation::Rep::ApplyToRemoteDocument( - const absl::optional& maybe_doc, - const MutationResult& mutation_result) const { - VerifyKeyMatches(maybe_doc); +void SetMutation::Rep::ApplyToRemoteDocument( + MutableDocument& document, const MutationResult& mutation_result) const { + VerifyKeyMatches(document); // Unlike ApplyToLocalView, if we're applying a mutation to a remote document // the server has accepted the mutation so the precondition must have held. - - ObjectValue new_data = value_; - if (mutation_result.transform_results() != absl::nullopt) { - std::vector transform_results = - ServerTransformResults(maybe_doc, *mutation_result.transform_results()); - new_data = TransformObject(new_data, transform_results); - } - - const SnapshotVersion& version = mutation_result.version(); - return Document(new_data, key(), version, DocumentState::kCommittedMutations); + auto transform_results = ServerTransformResults( + document.data(), mutation_result.transform_results()); + ObjectValue new_data{DeepClone(value_.Get())}; + new_data.SetAll(std::move(transform_results)); + document + .ConvertToFoundDocument(mutation_result.version(), std::move(new_data)) + .SetHasCommittedMutations(); } -absl::optional SetMutation::Rep::ApplyToLocalView( - const absl::optional& maybe_doc, - const Timestamp& local_write_time) const { - VerifyKeyMatches(maybe_doc); +void SetMutation::Rep::ApplyToLocalView( + MutableDocument& document, const Timestamp& local_write_time) const { + VerifyKeyMatches(document); - if (!precondition().IsValidFor(maybe_doc)) { - return maybe_doc; + if (!precondition().IsValidFor(document)) { + return; } - std::vector transforms_results = - LocalTransformResults(maybe_doc, local_write_time); - ObjectValue new_data = TransformObject(value_, transforms_results); - - SnapshotVersion version = GetPostMutationVersion(maybe_doc); - return Document(new_data, key(), version, DocumentState::kLocalMutations); + auto transform_results = + LocalTransformResults(document.data(), local_write_time); + ObjectValue new_data{DeepClone(value_.Get())}; + new_data.SetAll(std::move(transform_results)); + document + .ConvertToFoundDocument(GetPostMutationVersion(document), + std::move(new_data)) + .SetHasLocalMutations(); } bool SetMutation::Rep::Equals(const Mutation::Rep& other) const { diff --git a/Firestore/core/src/model/set_mutation.h b/Firestore/core/src/model/set_mutation.h index b2fa584a5ed..b876f7b981e 100644 --- a/Firestore/core/src/model/set_mutation.h +++ b/Firestore/core/src/model/set_mutation.h @@ -22,8 +22,8 @@ #include #include -#include "Firestore/core/src/model/field_value.h" #include "Firestore/core/src/model/mutation.h" +#include "Firestore/core/src/model/object_value.h" #include "absl/types/optional.h" namespace firebase { @@ -73,13 +73,12 @@ class SetMutation : public Mutation { return value_; } - MaybeDocument ApplyToRemoteDocument( - const absl::optional& maybe_doc, + void ApplyToRemoteDocument( + MutableDocument& document, const MutationResult& mutation_result) const override; - absl::optional ApplyToLocalView( - const absl::optional& maybe_doc, - const Timestamp& local_write_time) const override; + void ApplyToLocalView(MutableDocument& document, + const Timestamp& local_write_time) const override; bool Equals(const Mutation::Rep& other) const override; diff --git a/Firestore/core/src/model/transform_operation.cc b/Firestore/core/src/model/transform_operation.cc index 179d95878bf..1f016fc0f7c 100644 --- a/Firestore/core/src/model/transform_operation.cc +++ b/Firestore/core/src/model/transform_operation.cc @@ -16,13 +16,16 @@ #include "Firestore/core/src/model/transform_operation.h" +#include #include #include #include -#include #include "Firestore/core/include/firebase/firestore/timestamp.h" -#include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/model/server_timestamp_util.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" +#include "Firestore/core/src/util/comparison.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/to_string.h" #include "absl/algorithm/container.h" @@ -32,6 +35,7 @@ namespace firebase { namespace firestore { namespace model { +using nanopb::Message; using Type = TransformOperation::Type; // MARK: - TransformOperation @@ -62,20 +66,20 @@ class ServerTimestampTransform::Rep : public TransformOperation::Rep { return Type::ServerTimestamp; } - model::FieldValue ApplyToLocalView( - const absl::optional& previous_value, + Message ApplyToLocalView( + const absl::optional& previous_value, const Timestamp& local_write_time) const override { - return FieldValue::FromServerTimestamp(local_write_time, previous_value); + return EncodeServerTimestamp(local_write_time, previous_value); } - model::FieldValue ApplyToRemoteDocument( - const absl::optional&, - const model::FieldValue& transform_result) const override { + Message ApplyToRemoteDocument( + const absl::optional&, + Message transform_result) const override { return transform_result; } - absl::optional ComputeBaseValue( - const absl::optional&) const override { + absl::optional> ComputeBaseValue( + const absl::optional&) const override { // Server timestamps are idempotent and don't require a base value. return absl::nullopt; } @@ -110,37 +114,37 @@ static_assert(sizeof(TransformOperation) == sizeof(ArrayTransform), */ class ArrayTransform::Rep : public TransformOperation::Rep { public: - Rep(Type type, std::vector elements) - : type_(type), elements_(std::move(elements)) { + Rep(Type type, Message elements) + : type_(type), elements_{std::move(elements)} { } Type type() const override { return type_; } - model::FieldValue ApplyToLocalView( - const absl::optional& previous_value, + Message ApplyToLocalView( + const absl::optional& previous_value, const Timestamp&) const override { return Apply(previous_value); } - model::FieldValue ApplyToRemoteDocument( - const absl::optional& previous_value, - const model::FieldValue&) const override { + Message ApplyToRemoteDocument( + const absl::optional& previous_value, + Message) const override { // The server just sends null as the transform result for array operations, // so we have to calculate a result the same as we do for local // applications. return Apply(previous_value); } - absl::optional ComputeBaseValue( - const absl::optional&) const override { + absl::optional> ComputeBaseValue( + const absl::optional&) const override { // Array transforms are idempotent and don't require a base value. return absl::nullopt; } - const std::vector& elements() const { - return elements_; + google_firestore_v1_ArrayValue elements() const { + return *elements_; } bool Equals(const TransformOperation::Rep& other) const override; @@ -149,25 +153,22 @@ class ArrayTransform::Rep : public TransformOperation::Rep { std::string ToString() const override; - static const std::vector& Elements( - const TransformOperation& op); - private: friend class ArrayTransform; /** - * Inspects the provided value, returning a mutable copy of the internal array - * if it's of type Array and an empty mutable array if it's nil or any other - * type of FieldValue. + * Inspects the provided value, returning a copy of the internal array if it's + * of type Array and an empty array if it's nil or any other type of + * google_firestore_v1_Value. */ - static std::vector CoercedFieldValuesArray( - const absl::optional& value); + Message CoercedFieldValueArray( + const absl::optional& value) const; - model::FieldValue Apply( - const absl::optional& previous_value) const; + Message Apply( + const absl::optional& previous_value) const; Type type_; - std::vector elements_; + nanopb::Message elements_; }; namespace { @@ -179,7 +180,7 @@ constexpr bool IsArrayTransform(Type type) { } // namespace ArrayTransform::ArrayTransform(Type type, - std::vector elements) + Message elements) : TransformOperation( std::make_shared(type, std::move(elements))) { HARD_ASSERT(IsArrayTransform(type), "Expected array transform type; got %s", @@ -192,8 +193,8 @@ ArrayTransform::ArrayTransform(const TransformOperation& op) "Expected array transform type; got %s", op.type()); } -const std::vector& ArrayTransform::elements() const { - return array_rep().elements_; +google_firestore_v1_ArrayValue ArrayTransform::elements() const { + return *(array_rep().elements_); } const ArrayTransform::Rep& ArrayTransform::array_rep() const { @@ -204,12 +205,12 @@ bool ArrayTransform::Rep::Equals(const TransformOperation::Rep& other) const { if (other.type() != type()) { return false; } - auto other_rep = static_cast(other); - if (other_rep.elements_.size() != elements_.size()) { + auto& other_rep = static_cast(other); + if (other_rep.elements_->values_count != elements_->values_count) { return false; } - for (size_t i = 0; i < elements_.size(); i++) { - if (other_rep.elements_[i] != elements_[i]) { + for (pb_size_t i = 0; i < elements_->values_count; i++) { + if (other_rep.elements_->values[i] != elements_->values[i]) { return false; } } @@ -219,48 +220,74 @@ bool ArrayTransform::Rep::Equals(const TransformOperation::Rep& other) const { size_t ArrayTransform::Rep::Hash() const { size_t result = 37; result = 31 * result + (type() == Type::ArrayUnion ? 1231 : 1237); - for (const FieldValue& element : elements_) { - result = 31 * result + element.Hash(); + for (size_t i = 0; i < elements_->values_count; i++) { + result = 31 * result + + std::hash()(CanonicalId(elements_->values[i])); } return result; } std::string ArrayTransform::Rep::ToString() const { const char* name = type_ == Type::ArrayUnion ? "ArrayUnion" : "ArrayRemove"; - return absl::StrCat(name, "(", util::ToString(elements_), ")"); + return absl::StrCat(name, "(", CanonicalId(*elements_), ")"); } -FieldValue::Array ArrayTransform::Rep::CoercedFieldValuesArray( - const absl::optional& value) { - if (value && value->type() == FieldValue::Type::Array) { - return value->array_value(); +Message +ArrayTransform::Rep::CoercedFieldValueArray( + const absl::optional& value) const { + if (IsArray(value)) { + return DeepClone(value->array_value); } else { // coerce to empty array. return {}; } } -FieldValue ArrayTransform::Rep::Apply( - const absl::optional& previous_value) const { - FieldValue::Array result = CoercedFieldValuesArray(previous_value); - for (const FieldValue& element : elements_) { - auto pos = absl::c_find(result, element); - if (type_ == Type::ArrayUnion) { - if (pos == result.end()) { - result.push_back(element); +Message ArrayTransform::Rep::Apply( + const absl::optional& previous_value) const { + Message array_value = + CoercedFieldValueArray(previous_value); + if (type_ == Type::ArrayUnion) { + // Gather the list of elements that have to be added. + std::vector> new_elements; + for (pb_size_t i = 0; i < elements_->values_count; ++i) { + const google_firestore_v1_Value& new_element = elements_->values[i]; + if (!Contains(*array_value, new_element) && + !std::any_of(new_elements.begin(), new_elements.end(), + [&](const Message& value) { + return *value == new_element; + })) { + new_elements.push_back(DeepClone(new_element)); } - } else { - HARD_ASSERT(type_ == Type::ArrayRemove); - for (size_t i = 0; i < result.size();) { - if (element == result.at(i)) { - result.erase(result.cbegin() + i); - } else { - ++i; - } + } + + // Append the elements to the end of the list + size_t new_size = array_value->values_count + new_elements.size(); + array_value->values = nanopb::ResizeArray( + array_value->values, new_size); + for (auto& element : new_elements) { + array_value->values[array_value->values_count] = *element.release(); + ++array_value->values_count; + } + } else { + HARD_ASSERT(type_ == Type::ArrayRemove); + pb_size_t new_index = 0; + for (pb_size_t old_index = 0; old_index < array_value->values_count; + ++old_index) { + if (Contains(*elements_, array_value->values[old_index])) { + nanopb::FreeFieldsArray(&array_value->values[old_index]); + } else { + array_value->values[new_index] = array_value->values[old_index]; + ++new_index; } } + array_value->values_count = new_index; } - return FieldValue::FromArray(std::move(result)); + + Message result; + result->which_value_type = google_firestore_v1_Value_array_value_tag; + result->array_value = *array_value.release(); + return result; } // MARK: - NumericIncrementTransform @@ -270,49 +297,50 @@ static_assert(sizeof(TransformOperation) == sizeof(NumericIncrementTransform), class NumericIncrementTransform::Rep : public TransformOperation::Rep { public: - explicit Rep(model::FieldValue operand) : operand_(std::move(operand)) { + explicit Rep(Message operand) + : operand_(std::move(operand)) { } Type type() const override { return Type::Increment; } - model::FieldValue ApplyToLocalView( - const absl::optional& previous_value, + Message ApplyToLocalView( + const absl::optional& previous_value, const Timestamp& local_write_time) const override; - model::FieldValue ApplyToRemoteDocument( - const absl::optional&, - const model::FieldValue& transform_result) const override { + Message ApplyToRemoteDocument( + const absl::optional&, + Message transform_result) const override { return transform_result; } - absl::optional ComputeBaseValue( - const absl::optional& previous_value) const override; + absl::optional> ComputeBaseValue( + const absl::optional& previous_value) + const override; - model::FieldValue operand() const { - return operand_; - } + double OperandAsDouble() const; bool Equals(const TransformOperation::Rep& other) const override; size_t Hash() const override { - return operand_.Hash(); + return std::hash()(CanonicalId(*operand_)); } std::string ToString() const override { - return absl::StrCat("NumericIncrement(", operand_.ToString(), ")"); + return absl::StrCat("NumericIncrement(", operand_->ToString(), ")"); } private: friend class NumericIncrementTransform; - model::FieldValue operand_; + Message operand_{}; }; -NumericIncrementTransform::NumericIncrementTransform(FieldValue operand) - : TransformOperation(std::make_shared(operand)) { - HARD_ASSERT(operand.is_number()); +NumericIncrementTransform::NumericIncrementTransform( + Message operand) + : TransformOperation(std::make_shared(std::move(operand))) { + HARD_ASSERT(IsNumber(this->operand())); } NumericIncrementTransform::NumericIncrementTransform( @@ -322,8 +350,8 @@ NumericIncrementTransform::NumericIncrementTransform( op.type()); } -const FieldValue& NumericIncrementTransform::operand() const { - return static_cast(rep()).operand_; +const google_firestore_v1_Value& NumericIncrementTransform::operand() const { + return *static_cast(rep()).operand_; } namespace { @@ -344,44 +372,55 @@ int64_t SafeIncrement(int64_t x, int64_t y) { return x + y; } -double AsDouble(const FieldValue& value) { - if (value.type() == FieldValue::Type::Double) { - return value.double_value(); - } else if (value.type() == FieldValue::Type::Integer) { - return static_cast(value.integer_value()); +} // namespace + +double NumericIncrementTransform::Rep::OperandAsDouble() const { + if (IsDouble(*operand_)) { + return operand_->double_value; + } else if (IsInteger(*operand_)) { + return static_cast(operand_->integer_value); } else { HARD_FAIL("Expected 'operand' to be of numeric type, but was %s (type %s)", - value.ToString(), value.type()); + CanonicalId(*operand_), GetTypeOrder(*operand_)); } } -} // namespace - -FieldValue NumericIncrementTransform::Rep::ApplyToLocalView( - const absl::optional& previous_value, +Message +NumericIncrementTransform::Rep::ApplyToLocalView( + const absl::optional& previous_value, const Timestamp& /* local_write_time */) const { - absl::optional base_value = ComputeBaseValue(previous_value); + auto base_value = ComputeBaseValue(previous_value); + Message result; // Return an integer value only if the previous value and the operand is an // integer. - if (base_value && base_value->type() == FieldValue::Type::Integer && - operand_.type() == FieldValue::Type::Integer) { - int64_t sum = - SafeIncrement(base_value->integer_value(), operand_.integer_value()); - return FieldValue::FromInteger(sum); + if (IsInteger(**base_value) && IsInteger(*operand_)) { + result->which_value_type = google_firestore_v1_Value_integer_value_tag; + result->integer_value = + SafeIncrement((*base_value)->integer_value, operand_->integer_value); + } else if (IsInteger(**base_value)) { + result->which_value_type = google_firestore_v1_Value_double_value_tag; + result->double_value = (*base_value)->integer_value + OperandAsDouble(); } else { - HARD_ASSERT(base_value && base_value->is_number(), - "'base_value' is not of numeric type"); - double sum = AsDouble(*base_value) + AsDouble(operand_); - return FieldValue::FromDouble(sum); + HARD_ASSERT(IsDouble(**base_value), "'base_value' is not of numeric type"); + result->which_value_type = google_firestore_v1_Value_double_value_tag; + result->double_value = (*base_value)->double_value + OperandAsDouble(); } + + return result; } -absl::optional NumericIncrementTransform::Rep::ComputeBaseValue( - const absl::optional& previous_value) const { - return previous_value && previous_value->is_number() - ? previous_value - : absl::optional{FieldValue::FromInteger(0)}; +absl::optional> +NumericIncrementTransform::Rep::ComputeBaseValue( + const absl::optional& previous_value) const { + if (IsNumber(previous_value)) { + return DeepClone(*previous_value); + } + + Message zero_value; + zero_value->which_value_type = google_firestore_v1_Value_integer_value_tag; + zero_value->integer_value = 0; + return zero_value; } bool NumericIncrementTransform::Rep::Equals( @@ -390,8 +429,8 @@ bool NumericIncrementTransform::Rep::Equals( return false; } - auto other_rep = static_cast(other); - return operand_ == other_rep.operand_; + return *operand_ == + *static_cast(other).operand_; } } // namespace model diff --git a/Firestore/core/src/model/transform_operation.h b/Firestore/core/src/model/transform_operation.h index 7e7aee13e56..80411440b9b 100644 --- a/Firestore/core/src/model/transform_operation.h +++ b/Firestore/core/src/model/transform_operation.h @@ -23,7 +23,9 @@ #include #include -#include "Firestore/core/src/model/field_value.h" +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/core/src/nanopb/message.h" +#include "Firestore/core/src/util/hard_assert.h" #include "absl/types/optional.h" namespace firebase { @@ -75,8 +77,9 @@ class TransformOperation { * Computes the local transform result against the provided `previous_value`, * optionally using the provided local_write_time. */ - FieldValue ApplyToLocalView(const absl::optional& previous_value, - const Timestamp& local_write_time) const { + nanopb::Message ApplyToLocalView( + const absl::optional& previous_value, + const Timestamp& local_write_time) const { return rep().ApplyToLocalView(previous_value, local_write_time); } @@ -84,10 +87,11 @@ class TransformOperation { * Computes a final transform result after the transform has been acknowledged * by the server, potentially using the server-provided transform_result. */ - FieldValue ApplyToRemoteDocument( - const absl::optional& previous_value, - const FieldValue& transform_result) const { - return rep().ApplyToRemoteDocument(previous_value, transform_result); + nanopb::Message ApplyToRemoteDocument( + const absl::optional& previous_value, + nanopb::Message transform_result) const { + return rep().ApplyToRemoteDocument(previous_value, + std::move(transform_result)); } /** @@ -105,8 +109,8 @@ class TransformOperation { * @return a base value to store along with the mutation, or empty for * idempotent transforms. */ - absl::optional ComputeBaseValue( - const absl::optional& previous_value) const { + absl::optional> ComputeBaseValue( + const absl::optional& previous_value) const { return rep().ComputeBaseValue(previous_value); } @@ -133,16 +137,17 @@ class TransformOperation { virtual Type type() const = 0; - virtual FieldValue ApplyToLocalView( - const absl::optional& previous_value, + virtual nanopb::Message ApplyToLocalView( + const absl::optional& previous_value, const Timestamp& local_write_time) const = 0; - virtual FieldValue ApplyToRemoteDocument( - const absl::optional& previous_value, - const FieldValue& transform_result) const = 0; + virtual nanopb::Message ApplyToRemoteDocument( + const absl::optional& previous_value, + nanopb::Message transform_result) const = 0; - virtual absl::optional ComputeBaseValue( - const absl::optional& previous_value) const = 0; + virtual absl::optional> + ComputeBaseValue(const absl::optional& + previous_value) const = 0; virtual bool Equals(const TransformOperation::Rep& other) const = 0; @@ -176,7 +181,8 @@ class ServerTimestampTransform : public TransformOperation { */ class ArrayTransform : public TransformOperation { public: - ArrayTransform(Type type, std::vector elements); + ArrayTransform(Type type, + nanopb::Message array_value); /** * Casts a TransformOperation to an ArrayTransform. This is a checked @@ -185,9 +191,7 @@ class ArrayTransform : public TransformOperation { */ explicit ArrayTransform(const TransformOperation& op); - ArrayTransform() = default; - - const std::vector& elements() const; + google_firestore_v1_ArrayValue elements() const; private: class Rep; @@ -202,7 +206,8 @@ class ArrayTransform : public TransformOperation { */ class NumericIncrementTransform : public TransformOperation { public: - explicit NumericIncrementTransform(FieldValue operand); + explicit NumericIncrementTransform( + nanopb::Message operand); /** * Casts a TransformOperation to a NumericIncrementTransform. This is a @@ -211,7 +216,7 @@ class NumericIncrementTransform : public TransformOperation { */ explicit NumericIncrementTransform(const TransformOperation& op); - const FieldValue& operand() const; + const google_firestore_v1_Value& operand() const; private: class Rep; diff --git a/Firestore/core/src/model/unknown_document.cc b/Firestore/core/src/model/unknown_document.cc deleted file mode 100644 index 4fb362fc584..00000000000 --- a/Firestore/core/src/model/unknown_document.cc +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2018 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Firestore/core/src/model/unknown_document.h" - -#include -#include -#include - -#include "Firestore/core/src/util/hard_assert.h" -#include "absl/strings/str_cat.h" - -namespace firebase { -namespace firestore { -namespace model { - -static_assert( - sizeof(MaybeDocument) == sizeof(UnknownDocument), - "UnknownDocument may not have additional members (everything goes in Rep)"); - -class UnknownDocument::Rep : public MaybeDocument::Rep { - public: - Rep(DocumentKey key, SnapshotVersion version) - : MaybeDocument::Rep(Type::UnknownDocument, std::move(key), version) { - } - - bool has_pending_writes() const override { - // Unknown documents can only exist because of a logical inconsistency - // between the server successfully committing a mutation and our local - // cache believing it should not apply. We record UnknownDocuments to - // prevent flicker after the committed mutation is removed from the queue. - // If we ever read an UnknownDocument back, this means the cache entry for - // that document must be dirty. - return true; - } - - std::string ToString() const override { - return absl::StrCat("UnknownDocument(key=", key().ToString(), - ", version=", version().ToString(), ")"); - } -}; - -UnknownDocument::UnknownDocument(DocumentKey key, SnapshotVersion version) - : MaybeDocument(std::make_shared(std::move(key), version)) { -} - -UnknownDocument::UnknownDocument(const MaybeDocument& document) - : MaybeDocument(document) { - HARD_ASSERT(type() == Type::UnknownDocument); -} - -} // namespace model -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/src/model/unknown_document.h b/Firestore/core/src/model/unknown_document.h deleted file mode 100644 index 25d22dc67f5..00000000000 --- a/Firestore/core/src/model/unknown_document.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2018 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FIRESTORE_CORE_SRC_MODEL_UNKNOWN_DOCUMENT_H_ -#define FIRESTORE_CORE_SRC_MODEL_UNKNOWN_DOCUMENT_H_ - -#include "Firestore/core/src/model/maybe_document.h" - -namespace firebase { -namespace firestore { -namespace model { - -/** - * A class representing an existing document whose data is unknown (e.g. a - * document that was updated without a known base document). - */ -class UnknownDocument : public MaybeDocument { - public: - UnknownDocument(DocumentKey key, SnapshotVersion version); - - /** - * Casts a MaybeDocument to a UnknownDocument. This is a checked operation - * that will assert if the type of the MaybeDocument isn't actually - * Type::UnknownDocument. - */ - explicit UnknownDocument(const MaybeDocument& document); - - /** Creates an invalid UnknownDocument. */ - UnknownDocument() = default; - - private: - class Rep; -}; - -} // namespace model -} // namespace firestore -} // namespace firebase - -#endif // FIRESTORE_CORE_SRC_MODEL_UNKNOWN_DOCUMENT_H_ diff --git a/Firestore/core/src/model/value_util.cc b/Firestore/core/src/model/value_util.cc new file mode 100644 index 00000000000..70659b718d7 --- /dev/null +++ b/Firestore/core/src/model/value_util.cc @@ -0,0 +1,598 @@ +/* + * Copyright 2021 Google LLC + * + * 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/model/value_util.h" + +#include +#include +#include +#include +#include + +#include "Firestore/core/src/model/database_id.h" +#include "Firestore/core/src/model/document_key.h" +#include "Firestore/core/src/model/server_timestamp_util.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" +#include "Firestore/core/src/util/comparison.h" +#include "Firestore/core/src/util/hard_assert.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "absl/strings/str_split.h" + +namespace firebase { +namespace firestore { +namespace model { + +using nanopb::Message; +using util::ComparisonResult; + +TypeOrder GetTypeOrder(const google_firestore_v1_Value& value) { + switch (value.which_value_type) { + case google_firestore_v1_Value_null_value_tag: + return TypeOrder::kNull; + + case google_firestore_v1_Value_boolean_value_tag: + return TypeOrder::kBoolean; + + case google_firestore_v1_Value_integer_value_tag: + case google_firestore_v1_Value_double_value_tag: + return TypeOrder::kNumber; + + case google_firestore_v1_Value_timestamp_value_tag: + return TypeOrder::kTimestamp; + + case google_firestore_v1_Value_string_value_tag: + return TypeOrder::kString; + + case google_firestore_v1_Value_bytes_value_tag: + return TypeOrder::kBlob; + + case google_firestore_v1_Value_reference_value_tag: + return TypeOrder::kReference; + + case google_firestore_v1_Value_geo_point_value_tag: + return TypeOrder::kGeoPoint; + + case google_firestore_v1_Value_array_value_tag: + return TypeOrder::kArray; + + case google_firestore_v1_Value_map_value_tag: { + if (IsServerTimestamp(value)) { + return TypeOrder::kServerTimestamp; + } + return TypeOrder::kMap; + } + + default: + HARD_FAIL("Invalid type value: %s", value.which_value_type); + } +} + +void SortFields(google_firestore_v1_ArrayValue& value) { + for (pb_size_t i = 0; i < value.values_count; ++i) { + SortFields(value.values[i]); + } +} + +void SortFields(google_firestore_v1_Value& value) { + if (IsMap(value)) { + google_firestore_v1_MapValue& map_value = value.map_value; + std::sort(map_value.fields, map_value.fields + map_value.fields_count, + [](const google_firestore_v1_MapValue_FieldsEntry& lhs, + const google_firestore_v1_MapValue_FieldsEntry& rhs) { + return nanopb::MakeStringView(lhs.key) < + nanopb::MakeStringView(rhs.key); + }); + + for (pb_size_t i = 0; i < map_value.fields_count; ++i) { + SortFields(map_value.fields[i].value); + } + } else if (IsArray(value)) { + SortFields(value.array_value); + } +} + +ComparisonResult CompareNumbers(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right) { + if (left.which_value_type == google_firestore_v1_Value_double_value_tag) { + double left_double = left.double_value; + if (right.which_value_type == google_firestore_v1_Value_double_value_tag) { + return util::Compare(left_double, right.double_value); + } else { + return util::CompareMixedNumber(left_double, right.integer_value); + } + } else { + int64_t left_long = left.integer_value; + if (right.which_value_type == google_firestore_v1_Value_integer_value_tag) { + return util::Compare(left_long, right.integer_value); + } else { + return util::ReverseOrder( + util::CompareMixedNumber(right.double_value, left_long)); + } + } +} + +ComparisonResult CompareTimestamps(const google_protobuf_Timestamp& left, + const google_protobuf_Timestamp& right) { + ComparisonResult cmp = util::Compare(left.seconds, right.seconds); + if (cmp != ComparisonResult::Same) { + return cmp; + } + return util::Compare(left.nanos, right.nanos); +} + +ComparisonResult CompareStrings(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right) { + absl::string_view left_string = nanopb::MakeStringView(left.string_value); + absl::string_view right_string = nanopb::MakeStringView(right.string_value); + return util::Compare(left_string, right_string); +} + +ComparisonResult CompareBlobs(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right) { + if (left.bytes_value && right.bytes_value) { + size_t size = std::min(left.bytes_value->size, right.bytes_value->size); + int cmp = + std::memcmp(left.bytes_value->bytes, right.bytes_value->bytes, size); + return cmp != 0 + ? util::ComparisonResultFromInt(cmp) + : util::Compare(left.bytes_value->size, right.bytes_value->size); + } else { + // An empty blob is represented by a nullptr (or an empty byte array) + return util::Compare( + !(left.bytes_value == nullptr || left.bytes_value->size == 0), + !(right.bytes_value == nullptr || right.bytes_value->size == 0)); + } +} + +ComparisonResult CompareReferences(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right) { + std::vector left_segments = absl::StrSplit( + nanopb::MakeStringView(left.reference_value), '/', absl::SkipEmpty()); + std::vector right_segments = absl::StrSplit( + nanopb::MakeStringView(right.reference_value), '/', absl::SkipEmpty()); + + size_t min_length = std::min(left_segments.size(), right_segments.size()); + for (size_t i = 0; i < min_length; ++i) { + ComparisonResult cmp = util::Compare(left_segments[i], right_segments[i]); + if (cmp != ComparisonResult::Same) { + return cmp; + } + } + return util::Compare(left_segments.size(), right_segments.size()); +} + +ComparisonResult CompareGeoPoints(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right) { + ComparisonResult cmp = util::Compare(left.geo_point_value.latitude, + right.geo_point_value.latitude); + if (cmp != ComparisonResult::Same) { + return cmp; + } + return util::Compare(left.geo_point_value.longitude, + right.geo_point_value.longitude); +} + +ComparisonResult CompareArrays(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right) { + int min_length = + std::min(left.array_value.values_count, right.array_value.values_count); + for (int i = 0; i < min_length; ++i) { + ComparisonResult cmp = + Compare(left.array_value.values[i], right.array_value.values[i]); + if (cmp != ComparisonResult::Same) { + return cmp; + } + } + return util::Compare(left.array_value.values_count, + right.array_value.values_count); +} + +ComparisonResult CompareObjects(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right) { + google_firestore_v1_MapValue left_map = left.map_value; + google_firestore_v1_MapValue right_map = right.map_value; + + // Porting Note: MapValues in iOS are always kept in sorted order. We + // therefore do no need to sort them before comparing. + for (pb_size_t i = 0; i < left_map.fields_count && i < right_map.fields_count; + ++i) { + ComparisonResult key_cmp = + util::Compare(nanopb::MakeStringView(left_map.fields[i].key), + nanopb::MakeStringView(right_map.fields[i].key)); + if (key_cmp != ComparisonResult::Same) { + return key_cmp; + } + + ComparisonResult value_cmp = + Compare(left_map.fields[i].value, right.map_value.fields[i].value); + if (value_cmp != ComparisonResult::Same) { + return value_cmp; + } + } + + return util::Compare(left_map.fields_count, right_map.fields_count); +} + +ComparisonResult Compare(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right) { + TypeOrder left_type = GetTypeOrder(left); + TypeOrder right_type = GetTypeOrder(right); + + if (left_type != right_type) { + return util::Compare(left_type, right_type); + } + + switch (left_type) { + case TypeOrder::kNull: + return ComparisonResult::Same; + + case TypeOrder::kBoolean: + return util::Compare(left.boolean_value, right.boolean_value); + + case TypeOrder::kNumber: + return CompareNumbers(left, right); + + case TypeOrder::kTimestamp: + return CompareTimestamps(left.timestamp_value, right.timestamp_value); + + case TypeOrder::kServerTimestamp: + return CompareTimestamps(GetLocalWriteTime(left), + GetLocalWriteTime(right)); + + case TypeOrder::kString: + return CompareStrings(left, right); + + case TypeOrder::kBlob: + return CompareBlobs(left, right); + + case TypeOrder::kReference: + return CompareReferences(left, right); + + case TypeOrder::kGeoPoint: + return CompareGeoPoints(left, right); + + case TypeOrder::kArray: + return CompareArrays(left, right); + + case TypeOrder::kMap: + return CompareObjects(left, right); + + default: + HARD_FAIL("Invalid type value: %s", left_type); + } +} + +bool NumberEquals(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right) { + if (left.which_value_type == google_firestore_v1_Value_integer_value_tag && + right.which_value_type == google_firestore_v1_Value_integer_value_tag) { + return left.integer_value == right.integer_value; + } else if (left.which_value_type == + google_firestore_v1_Value_double_value_tag && + right.which_value_type == + google_firestore_v1_Value_double_value_tag) { + return util::DoubleBitwiseEquals(left.double_value, right.double_value); + } + return false; +} + +bool ArrayEquals(const google_firestore_v1_ArrayValue& left, + const google_firestore_v1_ArrayValue& right) { + if (left.values_count != right.values_count) { + return false; + } + + for (size_t i = 0; i < left.values_count; ++i) { + if (left.values[i] != right.values[i]) { + return false; + } + } + + return true; +} + +bool ObjectEquals(const google_firestore_v1_MapValue& left, + const google_firestore_v1_MapValue& right) { + if (left.fields_count != right.fields_count) { + return false; + } + + // Porting Note: MapValues in iOS are always kept in sorted order. We + // therefore do no need to sort them before comparing. + for (size_t i = 0; i < right.fields_count; ++i) { + if (nanopb::MakeStringView(left.fields[i].key) != + nanopb::MakeStringView(right.fields[i].key)) { + return false; + } + + if (left.fields[i].value != right.fields[i].value) { + return false; + } + } + + return true; +} + +bool Equals(const google_firestore_v1_Value& lhs, + const google_firestore_v1_Value& rhs) { + TypeOrder left_type = GetTypeOrder(lhs); + TypeOrder right_type = GetTypeOrder(rhs); + if (left_type != right_type) { + return false; + } + + switch (left_type) { + case TypeOrder::kNull: + return true; + + case TypeOrder::kBoolean: + return lhs.boolean_value == rhs.boolean_value; + + case TypeOrder::kNumber: + return NumberEquals(lhs, rhs); + + case TypeOrder::kTimestamp: + return lhs.timestamp_value.seconds == rhs.timestamp_value.seconds && + lhs.timestamp_value.nanos == rhs.timestamp_value.nanos; + + case TypeOrder::kServerTimestamp: { + const auto& left_ts = GetLocalWriteTime(lhs); + const auto& right_ts = GetLocalWriteTime(rhs); + return left_ts.seconds == right_ts.seconds && + left_ts.nanos == right_ts.nanos; + } + + case TypeOrder::kString: + return nanopb::MakeStringView(lhs.string_value) == + nanopb::MakeStringView(rhs.string_value); + + case TypeOrder::kBlob: + return CompareBlobs(lhs, rhs) == ComparisonResult::Same; + + case TypeOrder::kReference: + return nanopb::MakeStringView(lhs.reference_value) == + nanopb::MakeStringView(rhs.reference_value); + + case TypeOrder::kGeoPoint: + return lhs.geo_point_value.latitude == rhs.geo_point_value.latitude && + lhs.geo_point_value.longitude == rhs.geo_point_value.longitude; + + case TypeOrder::kArray: + return ArrayEquals(lhs.array_value, rhs.array_value); + + case TypeOrder::kMap: + return ObjectEquals(lhs.map_value, rhs.map_value); + + default: + HARD_FAIL("Invalid type value: %s", left_type); + } +} + +bool Equals(const google_firestore_v1_ArrayValue& lhs, + const google_firestore_v1_ArrayValue& rhs) { + return ArrayEquals(lhs, rhs); +} + +std::string CanonifyTimestamp(const google_firestore_v1_Value& value) { + return absl::StrFormat("time(%d,%d)", value.timestamp_value.seconds, + value.timestamp_value.nanos); +} + +std::string CanonifyBlob(const google_firestore_v1_Value& value) { + return absl::BytesToHexString(nanopb::MakeStringView(value.bytes_value)); +} + +std::string CanonifyReference(const google_firestore_v1_Value& value) { + std::vector segments = absl::StrSplit( + nanopb::MakeStringView(value.reference_value), '/', absl::SkipEmpty()); + HARD_ASSERT(segments.size() >= 5, + "Reference values should have at least 5 components"); + return absl::StrJoin(segments.begin() + 5, segments.end(), "/"); +} + +std::string CanonifyGeoPoint(const google_firestore_v1_Value& value) { + return absl::StrFormat("geo(%.1f,%.1f)", value.geo_point_value.latitude, + value.geo_point_value.longitude); +} + +std::string CanonifyArray(const google_firestore_v1_ArrayValue& array_value) { + std::string result = "["; + for (size_t i = 0; i < array_value.values_count; ++i) { + absl::StrAppend(&result, CanonicalId(array_value.values[i])); + if (i != array_value.values_count - 1) { + absl::StrAppend(&result, ","); + } + } + result += "]"; + return result; +} + +std::string CanonifyObject(const google_firestore_v1_Value& value) { + pb_size_t fields_count = value.map_value.fields_count; + const auto& fields = value.map_value.fields; + + // Porting Note: MapValues in iOS are always kept in sorted order. We + // therefore do no need to sort them before generating the canonical ID. + std::string result = "{"; + for (pb_size_t i = 0; i < fields_count; ++i) { + absl::StrAppend(&result, nanopb::MakeStringView(fields[i].key), ":", + CanonicalId(fields[i].value)); + if (i != fields_count - 1) { + absl::StrAppend(&result, ","); + } + } + result += "}"; + + return result; +} + +std::string CanonicalId(const google_firestore_v1_Value& value) { + switch (value.which_value_type) { + case google_firestore_v1_Value_null_value_tag: + return "null"; + + case google_firestore_v1_Value_boolean_value_tag: + return value.boolean_value ? "true" : "false"; + + case google_firestore_v1_Value_integer_value_tag: + return std::to_string(value.integer_value); + + case google_firestore_v1_Value_double_value_tag: + return absl::StrFormat("%.1f", value.double_value); + + case google_firestore_v1_Value_timestamp_value_tag: + return CanonifyTimestamp(value); + + case google_firestore_v1_Value_string_value_tag: + return nanopb::MakeString(value.string_value); + + case google_firestore_v1_Value_bytes_value_tag: + return CanonifyBlob(value); + + case google_firestore_v1_Value_reference_value_tag: + return CanonifyReference(value); + + case google_firestore_v1_Value_geo_point_value_tag: + return CanonifyGeoPoint(value); + + case google_firestore_v1_Value_array_value_tag: + return CanonifyArray(value.array_value); + + case google_firestore_v1_Value_map_value_tag: { + return CanonifyObject(value); + } + + default: + HARD_FAIL("Invalid type value: %s", value.which_value_type); + } +} + +std::string CanonicalId(const google_firestore_v1_ArrayValue& value) { + return CanonifyArray(value); +} + +bool Contains(google_firestore_v1_ArrayValue haystack, + google_firestore_v1_Value needle) { + for (pb_size_t i = 0; i < haystack.values_count; ++i) { + if (Equals(haystack.values[i], needle)) { + return true; + } + } + return false; +} + +Message NullValue() { + Message null_value; + null_value->which_value_type = google_firestore_v1_Value_null_value_tag; + null_value->null_value = {}; + return null_value; +} + +bool IsNullValue(const google_firestore_v1_Value& value) { + return value.which_value_type == google_firestore_v1_Value_null_value_tag; +} + +Message NaNValue() { + Message nan_value; + nan_value->which_value_type = google_firestore_v1_Value_double_value_tag; + nan_value->double_value = std::numeric_limits::quiet_NaN(); + return nan_value; +} + +bool IsNaNValue(const google_firestore_v1_Value& value) { + return value.which_value_type == google_firestore_v1_Value_double_value_tag && + std::isnan(value.double_value); +} + +Message RefValue( + const model::DatabaseId& database_id, + const model::DocumentKey& document_key) { + Message result; + result->which_value_type = google_firestore_v1_Value_reference_value_tag; + result->string_value = nanopb::MakeBytesArray(util::StringFormat( + "projects/%s/databases/%s/documents/%s", database_id.project_id(), + database_id.database_id(), document_key.ToString())); + return result; +} + +Message DeepClone( + const google_firestore_v1_Value& source) { + Message target{source}; + switch (source.which_value_type) { + case google_firestore_v1_Value_string_value_tag: + target->string_value = + source.string_value + ? nanopb::MakeBytesArray(source.string_value->bytes, + source.string_value->size) + : nullptr; + break; + + case google_firestore_v1_Value_reference_value_tag: + target->reference_value = nanopb::MakeBytesArray( + source.reference_value->bytes, source.reference_value->size); + break; + + case google_firestore_v1_Value_bytes_value_tag: + target->bytes_value = + source.bytes_value ? nanopb::MakeBytesArray(source.bytes_value->bytes, + source.bytes_value->size) + : nullptr; + break; + + case google_firestore_v1_Value_array_value_tag: + target->array_value.values_count = source.array_value.values_count; + target->array_value.values = nanopb::MakeArray( + source.array_value.values_count); + for (pb_size_t i = 0; i < source.array_value.values_count; ++i) { + target->array_value.values[i] = + *DeepClone(source.array_value.values[i]).release(); + } + break; + + case google_firestore_v1_Value_map_value_tag: + target->map_value.fields_count = source.map_value.fields_count; + target->map_value.fields = + nanopb::MakeArray( + source.map_value.fields_count); + for (pb_size_t i = 0; i < source.map_value.fields_count; ++i) { + target->map_value.fields[i].key = + nanopb::MakeBytesArray(source.map_value.fields[i].key->bytes, + source.map_value.fields[i].key->size); + target->map_value.fields[i].value = + *DeepClone(source.map_value.fields[i].value).release(); + } + break; + } + return target; +} + +Message DeepClone( + const google_firestore_v1_ArrayValue& source) { + Message target{source}; + target->values_count = source.values_count; + target->values = + nanopb::MakeArray(source.values_count); + for (pb_size_t i = 0; i < source.values_count; ++i) { + target->values[i] = *DeepClone(source.values[i]).release(); + } + return target; +} + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/src/model/value_util.h b/Firestore/core/src/model/value_util.h new file mode 100644 index 00000000000..24328abac7a --- /dev/null +++ b/Firestore/core/src/model/value_util.h @@ -0,0 +1,175 @@ +/* + * Copyright 2021 Google LLC + * + * 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_MODEL_VALUE_UTIL_H_ +#define FIRESTORE_CORE_SRC_MODEL_VALUE_UTIL_H_ + +#include +#include +#include + +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/core/src/nanopb/message.h" +#include "absl/types/optional.h" + +namespace firebase { +namespace firestore { + +namespace util { +enum class ComparisonResult; +} + +namespace model { + +class DocumentKey; +class DatabaseId; + +/** + * The order of types in Firestore. This order is based on the backend's + * ordering, but modified to support server timestamps. + */ +enum class TypeOrder { + kNull = 0, + kBoolean = 1, + kNumber = 2, + kTimestamp = 3, + kServerTimestamp = 4, + kString = 5, + kBlob = 6, + kReference = 7, + kGeoPoint = 8, + kArray = 9, + kMap = 10 +}; + +/** Returns the backend's type order of the given Value type. */ +TypeOrder GetTypeOrder(const google_firestore_v1_Value& value); + +/** Traverses a Value proto and sorts all MapValues by key. */ +void SortFields(google_firestore_v1_Value& value); + +/** Traverses an ArrayValue proto and sorts all MapValues by key. */ +void SortFields(google_firestore_v1_ArrayValue& value); + +util::ComparisonResult Compare(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right); + +bool Equals(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right); + +bool Equals(const google_firestore_v1_ArrayValue& left, + const google_firestore_v1_ArrayValue& right); + +/** + * Generates the canonical ID for the provided field value (as used in Target + * serialization). + */ +std::string CanonicalId(const google_firestore_v1_Value& value); + +/** + * Generates the canonical ID for the provided array value (as used in Target + * serialization). + */ +std::string CanonicalId(const google_firestore_v1_ArrayValue& value); + +/** Returns true if the array value contains the specified element. */ +bool Contains(google_firestore_v1_ArrayValue haystack, + google_firestore_v1_Value needle); + +/** Returns a null Protobuf value. */ +nanopb::Message NullValue(); + +/** Returns `true` if `value` is null in its Protobuf representation. */ +bool IsNullValue(const google_firestore_v1_Value& value); + +/** Returns `NaN` in its Protobuf representation. */ +nanopb::Message NaNValue(); + +/** Returns `true` if `value` is `NaN` in its Protobuf representation. */ +bool IsNaNValue(const google_firestore_v1_Value& value); + +/** Returns a Protobuf reference value representing the given location. */ +nanopb::Message RefValue( + const DatabaseId& database_id, const DocumentKey& document_key); + +/** Creates a copy of the contents of the Value proto. */ +nanopb::Message DeepClone( + const google_firestore_v1_Value& source); + +/** Creates a copy of the contents of the ArrayValue proto. */ +nanopb::Message DeepClone( + const google_firestore_v1_ArrayValue& source); + +/** Returns true if `value` is a INTEGER_VALUE. */ +inline bool IsInteger(const absl::optional& value) { + return value && + value->which_value_type == google_firestore_v1_Value_integer_value_tag; +} + +/** Returns true if `value` is a DOUBLE_VALUE. */ +inline bool IsDouble(const absl::optional& value) { + return value && + value->which_value_type == google_firestore_v1_Value_double_value_tag; +} + +/** Returns true if `value` is either a INTEGER_VALUE or a DOUBLE_VALUE. */ +inline bool IsNumber(const absl::optional& value) { + return IsInteger(value) || IsDouble(value); +} + +/** Returns true if `value` is an ARRAY_VALUE. */ +inline bool IsArray(const absl::optional& value) { + return value && + value->which_value_type == google_firestore_v1_Value_array_value_tag; +} + +/** Returns true if `value` is a MAP_VALUE. */ +inline bool IsMap(const absl::optional& value) { + return value && + value->which_value_type == google_firestore_v1_Value_map_value_tag; +} + +} // namespace model + +inline bool operator==(const google_firestore_v1_Value& lhs, + const google_firestore_v1_Value& rhs) { + return model::Equals(lhs, rhs); +} + +inline bool operator!=(const google_firestore_v1_Value& lhs, + const google_firestore_v1_Value& rhs) { + return !model::Equals(lhs, rhs); +} + +inline bool operator==(const google_firestore_v1_ArrayValue& lhs, + const google_firestore_v1_ArrayValue& rhs) { + return model::Equals(lhs, rhs); +} + +inline bool operator!=(const google_firestore_v1_ArrayValue& lhs, + const google_firestore_v1_ArrayValue& rhs) { + return !model::Equals(lhs, rhs); +} + +inline std::ostream& operator<<(std::ostream& out, + const google_firestore_v1_Value& value) { + return out << model::CanonicalId(value); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_SRC_MODEL_VALUE_UTIL_H_ diff --git a/Firestore/core/src/model/verify_mutation.cc b/Firestore/core/src/model/verify_mutation.cc index d18392a9441..456e606c4c0 100644 --- a/Firestore/core/src/model/verify_mutation.cc +++ b/Firestore/core/src/model/verify_mutation.cc @@ -19,7 +19,7 @@ #include #include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/no_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/util/hard_assert.h" namespace firebase { @@ -38,13 +38,13 @@ VerifyMutation::VerifyMutation(const Mutation& mutation) : Mutation(mutation) { HARD_ASSERT(type() == Type::Verify); } -MaybeDocument VerifyMutation::Rep::ApplyToRemoteDocument( - const absl::optional&, const MutationResult&) const { +void VerifyMutation::Rep::ApplyToRemoteDocument(MutableDocument&, + const MutationResult&) const { HARD_FAIL("VerifyMutation should only be used in Transactions."); } -absl::optional VerifyMutation::Rep::ApplyToLocalView( - const absl::optional&, const Timestamp&) const { +void VerifyMutation::Rep::ApplyToLocalView(MutableDocument&, + const Timestamp&) const { HARD_FAIL("VerifyMutation should only be used in Transactions."); } diff --git a/Firestore/core/src/model/verify_mutation.h b/Firestore/core/src/model/verify_mutation.h index eb21039aa12..778ba8f39f7 100644 --- a/Firestore/core/src/model/verify_mutation.h +++ b/Firestore/core/src/model/verify_mutation.h @@ -24,7 +24,6 @@ #include "Firestore/core/include/firebase/firestore/timestamp.h" #include "Firestore/core/src/model/document_key.h" -#include "Firestore/core/src/model/maybe_document.h" #include "Firestore/core/src/model/mutation.h" #include "Firestore/core/src/model/precondition.h" #include "Firestore/core/src/model/snapshot_version.h" @@ -34,6 +33,8 @@ namespace firebase { namespace firestore { namespace model { +class MutableDocument; + /** * A mutation that verifies the existence of the document at the given key * with the provided precondition. @@ -63,13 +64,12 @@ class VerifyMutation : public Mutation { return Type::Verify; } - MaybeDocument ApplyToRemoteDocument( - const absl::optional& maybe_doc, + void ApplyToRemoteDocument( + MutableDocument& document, const MutationResult& mutation_result) const override; - absl::optional ApplyToLocalView( - const absl::optional& maybe_doc, - const Timestamp&) const override; + void ApplyToLocalView(MutableDocument& document, + const Timestamp&) const override; // Does not override Equals or Hash; Mutation's versions are sufficient. diff --git a/Firestore/core/src/nanopb/fields_array.h b/Firestore/core/src/nanopb/fields_array.h index e6ad3d4f403..15849d4d0ab 100644 --- a/Firestore/core/src/nanopb/fields_array.h +++ b/Firestore/core/src/nanopb/fields_array.h @@ -133,6 +133,22 @@ inline const pb_field_t* FieldsArray() { return google_firestore_v1_Value_fields; } +template <> +inline const pb_field_t* FieldsArray() { + return google_firestore_v1_ArrayValue_fields; +} + +template <> +inline const pb_field_t* FieldsArray() { + return google_firestore_v1_MapValue_fields; +} + +template <> +inline const pb_field_t* +FieldsArray() { + return google_firestore_v1_MapValue_FieldsEntry_fields; +} + template <> inline const pb_field_t* FieldsArray() { return google_firestore_v1_Write_fields; diff --git a/Firestore/core/src/nanopb/message.h b/Firestore/core/src/nanopb/message.h index 305b34a930b..24e7e4296b4 100644 --- a/Firestore/core/src/nanopb/message.h +++ b/Firestore/core/src/nanopb/message.h @@ -17,6 +17,7 @@ #ifndef FIRESTORE_CORE_SRC_NANOPB_MESSAGE_H_ #define FIRESTORE_CORE_SRC_NANOPB_MESSAGE_H_ +#include #include #include @@ -65,6 +66,12 @@ class Message { */ Message() = default; + /** + * Creates a `Message` object that wraps `proto`. Takes ownership of `proto`. + */ + explicit Message(const T& proto) : owns_proto_(true), proto_(proto) { + } + /** * Attempts to parse a Nanopb message from the given `reader`. If the reader * contains ill-formed bytes, returns a default-constructed `Message`; check @@ -166,6 +173,14 @@ class Message { return proto_.ToString(); } + /** + * Returns false if the Message is not associated with a valid proto (for + * example, if it has been moved from). + */ + constexpr explicit operator bool() const noexcept { + return owns_proto_; + } + private: // Important: this function does *not* modify `owns_proto_`. void Free() { @@ -180,6 +195,69 @@ class Message { T proto_{}; }; +template +Message MakeMessage(const T& proto) { + return Message(proto); +} + +/** + * A wrapper of Message objects that facilitates shared ownership of Protobuf + * data. + */ +// TODO(mrschmidt): Add a template specialization +template +class SharedMessage { + public: + /** Creates a `SharedMessage` object that wraps `proto`. */ + SharedMessage(Message message) // NOLINT + : message_{std::make_shared>(*message.release())} { + } + + /** + * Returns a pointer to the underlying Nanopb proto. + */ + T* get() { + return message_->get(); + } + + /** + * Returns a pointer to the underlying Nanopb proto. + */ + const T* get() const { + return message_->get(); + } + + /** + * Returns a reference to the underlying Nanopb proto. + */ + T& operator*() { + return *get(); + } + + /** + * Returns a reference to the underlying Nanopb proto. + */ + const T& operator*() const { + return *get(); + } + + T* operator->() { + return get(); + } + + const T* operator->() const { + return get(); + } + + private: + std::shared_ptr> message_; +}; + +template +SharedMessage MakeSharedMessage(const T& proto) { + return SharedMessage(Message(proto)); +} + template Message Message::TryParse(Reader* reader) { Message result; @@ -221,6 +299,12 @@ std::string MakeStdString(const Message& message) { return writer.Release(); } +/** Free the dynamically-allocated memory for the fields array of type T. */ +template +void FreeFieldsArray(T* message) { + FreeNanopbMessage(FieldsArray(), message); +} + } // namespace nanopb } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/nanopb/nanopb_util.h b/Firestore/core/src/nanopb/nanopb_util.h index 580b17576d3..6c551735e3e 100644 --- a/Firestore/core/src/nanopb/nanopb_util.h +++ b/Firestore/core/src/nanopb/nanopb_util.h @@ -22,9 +22,11 @@ #include #include #include +#include #include #include "Firestore/core/src/nanopb/byte_string.h" +#include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/nullability.h" #include "absl/base/casts.h" #include "absl/memory/memory.h" @@ -104,6 +106,66 @@ T* _Nonnull MakeArray(pb_size_t count) { return static_cast(calloc(count, sizeof(T))); } +template +T* _Nonnull ResizeArray(void* _Nonnull ptr, size_t count) { + return static_cast(realloc(ptr, CheckedSize(count) * sizeof(T))); +} + +/** + * Initializes a repeated field with a list of values. Applies `converter` to + * each value before assigning. + */ +template +void SetRepeatedField(T* _Nonnull* _Nonnull fields_array, + pb_size_t* _Nonnull fields_count, + Iterator first, + Iterator last, + const Func& converter) { + HARD_ASSERT(fields_array, "fields_array must be non-null"); + HARD_ASSERT(fields_count, "fields_count must be non-null"); + *fields_count = nanopb::CheckedSize(std::distance(first, last)); + *fields_array = nanopb::MakeArray(*fields_count); + auto* current = *fields_array; + while (first != last) { + *current = converter(*first); + ++current; + ++first; + } +} + +/** + * Initializes a repeated field with a list of values. Applies `converter` to + * each value before assigning. + */ +template +void SetRepeatedField(T* _Nonnull* _Nonnull fields_array, + pb_size_t* _Nonnull fields_count, + const Container& fields, + const Func& converter) { + return SetRepeatedField(fields_array, fields_count, fields.begin(), + fields.end(), converter); +} + +/** Initializes a repeated field with a list of values. */ +template +void SetRepeatedField(T* _Nonnull* _Nonnull fields_array, + pb_size_t* _Nonnull fields_count, + const Container& fields) { + return SetRepeatedField(fields_array, fields_count, fields, + [](const T& val) { return val; }); +} + +/** + * Zeroes out the memory of `fields`. This can be used if the contents of fields + * array were moved to another message that takes on ownership. + */ +template +void ReleaseFieldOwnership(T* _Nonnull fields, pb_size_t fields_count) { + for (pb_size_t i = 0; i < fields_count; ++i) { + fields[i] = {}; + } +} + #if __OBJC__ inline ByteString MakeByteString(NSData* _Nullable value) { if (value == nil) return ByteString(); @@ -116,6 +178,10 @@ inline NSData* _Nonnull MakeNSData(const ByteString& str) { return [[NSData alloc] initWithBytes:str.data() length:str.size()]; } +inline NSData* _Nonnull MakeNSData(const pb_bytes_array_t* _Nullable data) { + return [[NSData alloc] initWithBytes:data->bytes length:data->size]; +} + inline NSData* _Nullable MakeNullableNSData(const ByteString& str) { if (str.empty()) return nil; return MakeNSData(str); diff --git a/Firestore/core/src/remote/datastore.cc b/Firestore/core/src/remote/datastore.cc index d8243a5fce5..6b4f19393cc 100644 --- a/Firestore/core/src/remote/datastore.cc +++ b/Firestore/core/src/remote/datastore.cc @@ -24,6 +24,7 @@ #include "Firestore/core/src/auth/token.h" #include "Firestore/core/src/core/database_info.h" #include "Firestore/core/src/model/database_id.h" +#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/mutation.h" #include "Firestore/core/src/remote/connectivity_monitor.h" diff --git a/Firestore/core/src/remote/datastore.h b/Firestore/core/src/remote/datastore.h index dbc13194bd8..a6860c879ae 100644 --- a/Firestore/core/src/remote/datastore.h +++ b/Firestore/core/src/remote/datastore.h @@ -40,6 +40,11 @@ namespace firebase { namespace firestore { + +namespace model { +class Document; +}; // namespace model + namespace remote { class ConnectivityMonitor; @@ -62,8 +67,8 @@ class FirebaseMetadataProvider; */ class Datastore : public std::enable_shared_from_this { public: - using LookupCallback = std::function>&)>; + using LookupCallback = + std::function>&)>; using CommitCallback = std::function; Datastore(const core::DatabaseInfo& database_info, @@ -169,9 +174,6 @@ class Datastore : public std::enable_shared_from_this { void RemoveGrpcCall(GrpcCall* to_remove); - static GrpcCall::Metadata ExtractAllowlistedHeaders( - const GrpcCall::Metadata& headers); - // In case Auth tries to invoke a callback after `Datastore` has been shut // down. bool is_shut_down_ = false; diff --git a/Firestore/core/src/remote/remote_event.cc b/Firestore/core/src/remote/remote_event.cc index 6d730d4ef16..f6f5d86ccca 100644 --- a/Firestore/core/src/remote/remote_event.cc +++ b/Firestore/core/src/remote/remote_event.cc @@ -19,7 +19,6 @@ #include #include "Firestore/core/src/local/target_data.h" -#include "Firestore/core/src/model/no_document.h" namespace firebase { namespace firestore { @@ -31,8 +30,7 @@ using local::QueryPurpose; using local::TargetData; using model::DocumentKey; using model::DocumentKeySet; -using model::MaybeDocument; -using model::NoDocument; +using model::MutableDocument; using model::SnapshotVersion; using model::TargetId; using nanopb::ByteString; @@ -125,7 +123,7 @@ void WatchChangeAggregator::HandleDocumentChange( const DocumentWatchChange& document_change) { for (TargetId target_id : document_change.updated_target_ids()) { const auto& new_doc = document_change.new_document(); - if (new_doc && new_doc->is_document()) { + if (new_doc && new_doc->is_found_document()) { AddDocumentToTarget(target_id, *new_doc); } else if (new_doc && new_doc->is_no_document()) { RemoveDocumentFromTarget(target_id, document_change.document_key(), @@ -228,8 +226,7 @@ void WatchChangeAggregator::HandleExistenceFilter( DocumentKey key{target.path()}; RemoveDocumentFromTarget( target_id, key, - NoDocument(key, SnapshotVersion::None(), - /* has_committed_mutations= */ false)); + MutableDocument::NoDocument(key, SnapshotVersion::None())); } else { HARD_ASSERT(expected_count == 1, "Single document existence filter with count: %s", @@ -270,8 +267,7 @@ RemoteEvent WatchChangeAggregator::CreateRemoteEvent( !TargetContainsDocument(target_id, key)) { RemoveDocumentFromTarget( target_id, key, - NoDocument(key, snapshot_version, - /* has_committed_mutations= */ false)); + MutableDocument::NoDocument(key, snapshot_version)); } } @@ -320,8 +316,8 @@ RemoteEvent WatchChangeAggregator::CreateRemoteEvent( return remote_event; } -void WatchChangeAggregator::AddDocumentToTarget(TargetId target_id, - const MaybeDocument& document) { +void WatchChangeAggregator::AddDocumentToTarget( + TargetId target_id, const MutableDocument& document) { if (!IsActiveTarget(target_id)) { return; } @@ -341,7 +337,7 @@ void WatchChangeAggregator::AddDocumentToTarget(TargetId target_id, void WatchChangeAggregator::RemoveDocumentFromTarget( TargetId target_id, const DocumentKey& key, - const absl::optional& updated_document) { + const absl::optional& updated_document) { if (!IsActiveTarget(target_id)) { return; } diff --git a/Firestore/core/src/remote/remote_event.h b/Firestore/core/src/remote/remote_event.h index 8cc8f60f44f..ef7943c9f03 100644 --- a/Firestore/core/src/remote/remote_event.h +++ b/Firestore/core/src/remote/remote_event.h @@ -26,7 +26,7 @@ #include "Firestore/core/src/core/view_snapshot.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/document_key_set.h" -#include "Firestore/core/src/model/maybe_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/snapshot_version.h" #include "Firestore/core/src/model/types.h" #include "Firestore/core/src/nanopb/byte_string.h" @@ -353,7 +353,7 @@ class WatchChangeAggregator { * document key to the given target's mapping. */ void AddDocumentToTarget(model::TargetId target_id, - const model::MaybeDocument& document); + const model::MutableDocument& document); /** * Removes the provided document from the target mapping. If the document no @@ -365,7 +365,7 @@ class WatchChangeAggregator { void RemoveDocumentFromTarget( model::TargetId target_id, const model::DocumentKey& key, - const absl::optional& updated_document); + const absl::optional& updated_document); /** * Returns the current count of documents in the target. This includes both diff --git a/Firestore/core/src/remote/remote_objc_bridge.cc b/Firestore/core/src/remote/remote_objc_bridge.cc index 547d46d95e3..1ceba457cf8 100644 --- a/Firestore/core/src/remote/remote_objc_bridge.cc +++ b/Firestore/core/src/remote/remote_objc_bridge.cc @@ -19,8 +19,8 @@ #include #include "Firestore/core/src/core/database_info.h" +#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_key.h" -#include "Firestore/core/src/model/maybe_document.h" #include "Firestore/core/src/model/mutation.h" #include "Firestore/core/src/model/snapshot_version.h" #include "Firestore/core/src/nanopb/byte_string.h" @@ -40,8 +40,8 @@ namespace remote { using core::DatabaseInfo; using local::TargetData; +using model::Document; using model::DocumentKey; -using model::MaybeDocument; using model::Mutation; using model::MutationResult; using model::SnapshotVersion; @@ -104,7 +104,7 @@ WatchStreamSerializer::ParseResponse(Reader* reader) const { std::unique_ptr WatchStreamSerializer::DecodeWatchChange( nanopb::Reader* reader, - const google_firestore_v1_ListenResponse& response) const { + google_firestore_v1_ListenResponse& response) const { return serializer_.DecodeWatchChange(reader->context(), response); } @@ -170,14 +170,13 @@ SnapshotVersion WriteStreamSerializer::DecodeCommitVersion( } std::vector WriteStreamSerializer::DecodeMutationResults( - nanopb::Reader* reader, - const google_firestore_v1_WriteResponse& proto) const { + nanopb::Reader* reader, google_firestore_v1_WriteResponse& proto) const { SnapshotVersion commit_version = DecodeCommitVersion(reader, proto); if (!reader->ok()) { return {}; } - const google_firestore_v1_WriteResult* writes = proto.write_results; + google_firestore_v1_WriteResult* writes = proto.write_results; pb_size_t count = proto.write_results_count; std::vector results; results.reserve(count); @@ -235,11 +234,11 @@ DatastoreSerializer::EncodeLookupRequest( return result; } -StatusOr> +StatusOr> DatastoreSerializer::MergeLookupResponses( const std::vector& responses) const { // Sort by key. - std::map results; + std::map results; for (const auto& response : responses) { ByteBufferReader reader{response}; @@ -247,22 +246,21 @@ DatastoreSerializer::MergeLookupResponses( Message::TryParse( &reader); - MaybeDocument doc = - serializer_.DecodeMaybeDocument(reader.context(), *message); + Document doc = serializer_.DecodeMaybeDocument(reader.context(), *message); if (!reader.ok()) { return reader.status(); } - results[doc.key()] = std::move(doc); + results[doc->key()] = std::move(doc); } - std::vector docs; + std::vector docs; docs.reserve(results.size()); for (const auto& kv : results) { docs.push_back(kv.second); } - StatusOr> result{std::move(docs)}; + StatusOr> result{std::move(docs)}; return result; } diff --git a/Firestore/core/src/remote/remote_objc_bridge.h b/Firestore/core/src/remote/remote_objc_bridge.h index d7941fced2c..7d074800630 100644 --- a/Firestore/core/src/remote/remote_objc_bridge.h +++ b/Firestore/core/src/remote/remote_objc_bridge.h @@ -41,7 +41,6 @@ class TargetData; namespace model { class DocumentKey; -class MaybeDocument; class SnapshotVersion; } // namespace model @@ -75,9 +74,13 @@ class WatchStreamSerializer { nanopb::Message ParseResponse( nanopb::Reader* reader) const; + /** + * Decodes the listen response. Modifies the provided proto to release + * ownership of any Value messages. + */ std::unique_ptr DecodeWatchChange( nanopb::Reader* reader, - const google_firestore_v1_ListenResponse& response) const; + google_firestore_v1_ListenResponse& response) const; model::SnapshotVersion DecodeSnapshotVersion( nanopb::Reader* reader, const google_firestore_v1_ListenResponse& response) const; @@ -102,9 +105,12 @@ class WriteStreamSerializer { model::SnapshotVersion DecodeCommitVersion( nanopb::Reader* reader, const google_firestore_v1_WriteResponse& proto) const; + /** + * Decodes the write response. Modifies the provided proto to release + * ownership of any Value messages. + */ std::vector DecodeMutationResults( - nanopb::Reader* reader, - const google_firestore_v1_WriteResponse& proto) const; + nanopb::Reader* reader, google_firestore_v1_WriteResponse& proto) const; private: Serializer serializer_; @@ -124,7 +130,7 @@ class DatastoreSerializer { * Merges results of the streaming read together. The array is sorted by the * document key. */ - util::StatusOr> MergeLookupResponses( + util::StatusOr> MergeLookupResponses( const std::vector& responses) const; const Serializer& serializer() const { diff --git a/Firestore/core/src/remote/remote_store.cc b/Firestore/core/src/remote/remote_store.cc index 8caaa2d758c..f9658d25e3e 100644 --- a/Firestore/core/src/remote/remote_store.cc +++ b/Firestore/core/src/remote/remote_store.cc @@ -442,7 +442,7 @@ void RemoteStore::OnWriteStreamMutationResult( MutationBatchResult batch_result(std::move(batch), commit_version, std::move(mutation_results), write_stream_->last_stream_token()); - sync_engine_->HandleSuccessfulWrite(batch_result); + sync_engine_->HandleSuccessfulWrite(std::move(batch_result)); // It's possible that with the completion of this mutation another slot has // freed up. diff --git a/Firestore/core/src/remote/remote_store.h b/Firestore/core/src/remote/remote_store.h index cc16c92f94e..56d5b2cc057 100644 --- a/Firestore/core/src/remote/remote_store.h +++ b/Firestore/core/src/remote/remote_store.h @@ -79,7 +79,7 @@ class RemoteStoreCallback { * removing the batch from the mutation queue. */ virtual void HandleSuccessfulWrite( - const model::MutationBatchResult& batch_result) = 0; + model::MutationBatchResult batch_result) = 0; /** * Rejects the batch, removing the batch from the mutation queue, recomputing diff --git a/Firestore/core/src/remote/serializer.cc b/Firestore/core/src/remote/serializer.cc index 0bdb829267a..a232c05c2ae 100644 --- a/Firestore/core/src/remote/serializer.cc +++ b/Firestore/core/src/remote/serializer.cc @@ -36,13 +36,13 @@ #include "Firestore/core/src/core/query.h" #include "Firestore/core/src/local/target_data.h" #include "Firestore/core/src/model/delete_mutation.h" -#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/model/no_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/patch_mutation.h" #include "Firestore/core/src/model/resource_path.h" +#include "Firestore/core/src/model/server_timestamp_util.h" #include "Firestore/core/src/model/set_mutation.h" +#include "Firestore/core/src/model/value_util.h" #include "Firestore/core/src/model/verify_mutation.h" #include "Firestore/core/src/nanopb/byte_string.h" #include "Firestore/core/src/nanopb/nanopb_util.h" @@ -54,6 +54,7 @@ #include "Firestore/core/src/util/statusor.h" #include "Firestore/core/src/util/string_format.h" #include "absl/algorithm/container.h" +#include "absl/types/span.h" namespace firebase { namespace firestore { @@ -73,18 +74,20 @@ using local::QueryPurpose; using local::TargetData; using model::ArrayTransform; using model::DatabaseId; +using model::DeepClone; using model::DeleteMutation; -using model::Document; using model::DocumentKey; -using model::DocumentState; +using model::EncodeServerTimestamp; using model::FieldMask; using model::FieldPath; using model::FieldTransform; -using model::FieldValue; -using model::MaybeDocument; +using model::IsNaNValue; +using model::IsNullValue; +using model::MutableDocument; using model::Mutation; using model::MutationResult; -using model::NoDocument; +using model::NaNValue; +using model::NullValue; using model::NumericIncrementTransform; using model::ObjectValue; using model::PatchMutation; @@ -99,8 +102,15 @@ using model::VerifyMutation; using nanopb::ByteString; using nanopb::CheckedSize; using nanopb::MakeArray; +using nanopb::MakeMessage; +using nanopb::MakeSharedMessage; using nanopb::MakeStringView; +using nanopb::Message; +using nanopb::ReleaseFieldOwnership; using nanopb::SafeReadBoolean; +using nanopb::SetRepeatedField; +using nanopb::SharedMessage; +using nanopb::Writer; using remote::WatchChange; using util::ReadContext; using util::Status; @@ -154,7 +164,8 @@ ResourcePath ExtractLocalPathFromResourceName( Filter InvalidFilter() { // The exact value doesn't matter. Note that there's no way to create the base // class `Filter`, so it has to be one of the derived classes. - return FieldFilter::Create({}, {}, {}); + return FieldFilter::Create({}, {}, + MakeSharedMessage(google_firestore_v1_Value{})); } FieldPath InvalidFieldPath() { @@ -171,236 +182,6 @@ pb_bytes_array_t* Serializer::EncodeDatabaseName() const { return EncodeString(DatabaseName(database_id_).CanonicalString()); } -google_firestore_v1_Value Serializer::EncodeFieldValue( - const FieldValue& field_value) const { - switch (field_value.type()) { - case FieldValue::Type::Null: - return EncodeNull(); - - case FieldValue::Type::Boolean: - return EncodeBoolean(field_value.boolean_value()); - - case FieldValue::Type::Integer: - return EncodeInteger(field_value.integer_value()); - - case FieldValue::Type::Double: - return EncodeDouble(field_value.double_value()); - - case FieldValue::Type::Timestamp: - return EncodeTimestampValue(field_value.timestamp_value()); - - case FieldValue::Type::String: - return EncodeStringValue(field_value.string_value()); - - case FieldValue::Type::Blob: - return EncodeBlob(field_value.blob_value()); - - case FieldValue::Type::Reference: - return EncodeReference(field_value.reference_value()); - - case FieldValue::Type::GeoPoint: - return EncodeGeoPoint(field_value.geo_point_value()); - - case FieldValue::Type::Array: { - google_firestore_v1_Value result{}; - result.which_value_type = google_firestore_v1_Value_array_value_tag; - result.array_value = EncodeArray(field_value.array_value()); - return result; - } - - case FieldValue::Type::Object: { - google_firestore_v1_Value result{}; - result.which_value_type = google_firestore_v1_Value_map_value_tag; - result.map_value = EncodeMapValue(ObjectValue(field_value)); - return result; - } - - case FieldValue::Type::ServerTimestamp: - HARD_FAIL("Unhandled type %s on %s", field_value.type(), - field_value.ToString()); - } - UNREACHABLE(); -} - -google_firestore_v1_Value Serializer::EncodeNull() const { - google_firestore_v1_Value result{}; - result.which_value_type = google_firestore_v1_Value_null_value_tag; - result.null_value = google_protobuf_NullValue_NULL_VALUE; - return result; -} - -google_firestore_v1_Value Serializer::EncodeBoolean(bool value) const { - google_firestore_v1_Value result{}; - result.which_value_type = google_firestore_v1_Value_boolean_value_tag; - result.boolean_value = value; - return result; -} - -google_firestore_v1_Value Serializer::EncodeInteger(int64_t value) const { - google_firestore_v1_Value result{}; - result.which_value_type = google_firestore_v1_Value_integer_value_tag; - result.integer_value = value; - return result; -} - -google_firestore_v1_Value Serializer::EncodeDouble(double value) const { - google_firestore_v1_Value result{}; - result.which_value_type = google_firestore_v1_Value_double_value_tag; - result.double_value = value; - return result; -} - -google_firestore_v1_Value Serializer::EncodeTimestampValue( - Timestamp value) const { - google_firestore_v1_Value result{}; - result.which_value_type = google_firestore_v1_Value_timestamp_value_tag; - result.timestamp_value = EncodeTimestamp(value); - return result; -} - -google_firestore_v1_Value Serializer::EncodeStringValue( - const std::string& value) const { - google_firestore_v1_Value result{}; - result.which_value_type = google_firestore_v1_Value_string_value_tag; - result.string_value = EncodeString(value); - return result; -} - -google_firestore_v1_Value Serializer::EncodeBlob( - const nanopb::ByteString& value) const { - google_firestore_v1_Value result{}; - result.which_value_type = google_firestore_v1_Value_bytes_value_tag; - // Copy the blob so that pb_release can do the right thing. - result.bytes_value = nanopb::CopyBytesArray(value.get()); - return result; -} - -google_firestore_v1_Value Serializer::EncodeReference( - const FieldValue::Reference& value) const { - HARD_ASSERT(database_id_ == value.database_id(), - "Database %s cannot encode reference from %s", - database_id_.ToString(), value.database_id().ToString()); - - google_firestore_v1_Value result{}; - result.which_value_type = google_firestore_v1_Value_reference_value_tag; - result.reference_value = - EncodeResourceName(value.database_id(), value.key().path()); - - return result; -} - -google_firestore_v1_Value Serializer::EncodeGeoPoint( - const GeoPoint& value) const { - google_firestore_v1_Value result{}; - result.which_value_type = google_firestore_v1_Value_geo_point_value_tag; - - google_type_LatLng geo_point{}; - geo_point.latitude = value.latitude(); - geo_point.longitude = value.longitude(); - result.geo_point_value = geo_point; - - return result; -} - -FieldValue::Map::value_type Serializer::DecodeFieldsEntry( - ReadContext* context, - const google_firestore_v1_Document_FieldsEntry& fields) const { - std::string key = DecodeString(fields.key); - FieldValue value = DecodeFieldValue(context, fields.value); - - if (key.empty()) { - context->Fail( - "Invalid message: Empty key while decoding a Map field value."); - return {}; - } - - return FieldValue::Map::value_type{std::move(key), std::move(value)}; -} - -ObjectValue Serializer::DecodeFields( - ReadContext* context, - size_t count, - const google_firestore_v1_Document_FieldsEntry* fields) const { - FieldValue::Map result; - for (size_t i = 0; i < count; i++) { - FieldValue::Map::value_type kv = DecodeFieldsEntry(context, fields[i]); - result = result.insert(std::move(kv.first), std::move(kv.second)); - } - - return ObjectValue::FromMap(result); -} - -FieldValue::Map Serializer::DecodeMapValue( - ReadContext* context, const google_firestore_v1_MapValue& map_value) const { - FieldValue::Map result; - - for (size_t i = 0; i < map_value.fields_count; i++) { - std::string key = DecodeString(map_value.fields[i].key); - FieldValue value = DecodeFieldValue(context, map_value.fields[i].value); - if (!context->status().ok()) { - return FieldValue::Map{}; - } - - result = result.insert(key, value); - } - - return result; -} - -FieldValue Serializer::DecodeFieldValue( - ReadContext* context, const google_firestore_v1_Value& msg) const { - switch (msg.which_value_type) { - case google_firestore_v1_Value_null_value_tag: - if (msg.null_value != google_protobuf_NullValue_NULL_VALUE) { - context->Fail( - "Input proto bytes cannot be parsed (invalid null value)"); - } - return FieldValue::Null(); - - case google_firestore_v1_Value_boolean_value_tag: { - return FieldValue::FromBoolean(SafeReadBoolean(msg.boolean_value)); - } - - case google_firestore_v1_Value_integer_value_tag: - return FieldValue::FromInteger(msg.integer_value); - - case google_firestore_v1_Value_double_value_tag: - return FieldValue::FromDouble(msg.double_value); - - case google_firestore_v1_Value_timestamp_value_tag: { - return FieldValue::FromTimestamp( - DecodeTimestamp(context, msg.timestamp_value)); - } - - case google_firestore_v1_Value_string_value_tag: - return FieldValue::FromString(DecodeString(msg.string_value)); - - case google_firestore_v1_Value_bytes_value_tag: - return FieldValue::FromBlob(ByteString(msg.bytes_value)); - - case google_firestore_v1_Value_reference_value_tag: - return DecodeReference(context, msg.reference_value); - - case google_firestore_v1_Value_geo_point_value_tag: - return FieldValue::FromGeoPoint( - DecodeGeoPoint(context, msg.geo_point_value)); - - case google_firestore_v1_Value_array_value_tag: - return FieldValue::FromArray(DecodeArray(context, msg.array_value)); - - case google_firestore_v1_Value_map_value_tag: { - return FieldValue::FromMap(DecodeMapValue(context, msg.map_value)); - } - - default: - context->Fail(StringFormat("Invalid type while decoding FieldValue: %s", - msg.which_value_type)); - return FieldValue::Null(); - } - - UNREACHABLE(); -} - pb_bytes_array_t* Serializer::EncodeKey(const DocumentKey& key) const { return EncodeResourceName(database_id_, key.path()); } @@ -479,27 +260,14 @@ pb_bytes_array_t* Serializer::EncodeResourceName( ResourcePath Serializer::DecodeResourceName(ReadContext* context, absl::string_view encoded) const { - ResourcePath resource = ResourcePath::FromStringView(encoded); + auto resource = ResourcePath::FromStringView(encoded); if (!IsValidResourceName(resource)) { - context->Fail(StringFormat("Tried to deserialize an invalid key %s", + context->Fail(StringFormat("Tried to deserialize an invalid key: %s", resource.CanonicalString())); } return resource; } -DatabaseId Serializer::DecodeDatabaseId( - ReadContext* context, const ResourcePath& resource_name) const { - if (resource_name.size() < 4) { - context->Fail(StringFormat("Tried to deserialize invalid key %s", - resource_name.CanonicalString())); - return DatabaseId{}; - } - - const std::string& project_id = resource_name[1]; - const std::string& database_id = resource_name[3]; - return DatabaseId{project_id, database_id}; -} - google_firestore_v1_Document Serializer::EncodeDocument( const DocumentKey& key, const ObjectValue& object_value) const { google_firestore_v1_Document result{}; @@ -507,15 +275,17 @@ google_firestore_v1_Document Serializer::EncodeDocument( result.name = EncodeKey(key); // Encode Document.fields (unless it's empty) - pb_size_t count = CheckedSize(object_value.GetInternalValue().size()); - result.fields_count = count; - result.fields = MakeArray(count); - int i = 0; - for (const auto& kv : object_value.GetInternalValue()) { - result.fields[i].key = EncodeString(kv.first); - result.fields[i].value = EncodeFieldValue(kv.second); - i++; - } + const google_firestore_v1_MapValue& map_value = object_value.Get().map_value; + SetRepeatedField( + &result.fields, &result.fields_count, + absl::Span( + map_value.fields, map_value.fields_count), + [](const google_firestore_v1_MapValue_FieldsEntry& entry) { + // TODO(mrschmidt): Figure out how to remove this copy + return google_firestore_v1_Document_FieldsEntry{ + nanopb::MakeBytesArray(entry.key->bytes, entry.key->size), + *DeepClone(entry.value).release()}; + }); // Skip Document.create_time and Document.update_time, since they're // output-only fields. @@ -523,9 +293,9 @@ google_firestore_v1_Document Serializer::EncodeDocument( return result; } -MaybeDocument Serializer::DecodeMaybeDocument( +MutableDocument Serializer::DecodeMaybeDocument( ReadContext* context, - const google_firestore_v1_BatchGetDocumentsResponse& response) const { + google_firestore_v1_BatchGetDocumentsResponse& response) const { switch (response.which_result) { case google_firestore_v1_BatchGetDocumentsResponse_found_tag: return DecodeFoundDocument(context, response); @@ -534,33 +304,33 @@ MaybeDocument Serializer::DecodeMaybeDocument( default: context->Fail( StringFormat("Unknown result case: %s", response.which_result)); - return {}; + return MutableDocument::InvalidDocument({}); } UNREACHABLE(); } -Document Serializer::DecodeFoundDocument( +MutableDocument Serializer::DecodeFoundDocument( ReadContext* context, - const google_firestore_v1_BatchGetDocumentsResponse& response) const { + google_firestore_v1_BatchGetDocumentsResponse& response) const { HARD_ASSERT(response.which_result == google_firestore_v1_BatchGetDocumentsResponse_found_tag, "Tried to deserialize a found document from a missing document."); DocumentKey key = DecodeKey(context, response.found.name); - ObjectValue value = - DecodeFields(context, response.found.fields_count, response.found.fields); + ObjectValue value = ObjectValue::FromFieldsEntry(response.found.fields, + response.found.fields_count); SnapshotVersion version = DecodeVersion(context, response.found.update_time); if (version == SnapshotVersion::None()) { context->Fail("Got a document response with no snapshot version"); } - return Document(std::move(value), std::move(key), version, - DocumentState::kSynced); + return MutableDocument::FoundDocument(std::move(key), version, + std::move(value)); } -NoDocument Serializer::DecodeMissingDocument( +MutableDocument Serializer::DecodeMissingDocument( ReadContext* context, const google_firestore_v1_BatchGetDocumentsResponse& response) const { HARD_ASSERT(response.which_result == @@ -572,11 +342,9 @@ NoDocument Serializer::DecodeMissingDocument( if (version == SnapshotVersion::None()) { context->Fail("Got a no document response with no snapshot version"); - return {}; } - return NoDocument(std::move(key), version, - /*has_committed_mutations=*/false); + return MutableDocument::NoDocument(std::move(key), version); } google_firestore_v1_Write Serializer::EncodeMutation( @@ -589,15 +357,10 @@ google_firestore_v1_Write Serializer::EncodeMutation( result.current_document = EncodePrecondition(mutation.precondition()); } - pb_size_t count = CheckedSize(mutation.field_transforms().size()); - result.update_transforms_count = count; - result.update_transforms = - MakeArray(count); - int i = 0; - for (const FieldTransform& field_transform : mutation.field_transforms()) { - result.update_transforms[i] = EncodeFieldTransform(field_transform); - ++i; - } + SetRepeatedField(&result.update_transforms, &result.update_transforms_count, + mutation.field_transforms(), [&](const FieldTransform& t) { + return EncodeFieldTransform(t); + }); switch (mutation.type()) { case Mutation::Type::Set: { @@ -637,8 +400,8 @@ google_firestore_v1_Write Serializer::EncodeMutation( UNREACHABLE(); } -Mutation Serializer::DecodeMutation( - ReadContext* context, const google_firestore_v1_Write& mutation) const { +Mutation Serializer::DecodeMutation(ReadContext* context, + google_firestore_v1_Write& mutation) const { auto precondition = Precondition::None(); if (mutation.has_current_document) { precondition = DecodePrecondition(context, mutation.current_document); @@ -653,8 +416,8 @@ Mutation Serializer::DecodeMutation( switch (mutation.which_operation) { case google_firestore_v1_Write_update_tag: { DocumentKey key = DecodeKey(context, mutation.update.name); - ObjectValue value = DecodeFields(context, mutation.update.fields_count, - mutation.update.fields); + ObjectValue value = ObjectValue::FromFieldsEntry( + mutation.update.fields, mutation.update.fields_count); if (mutation.has_update_mask) { FieldMask mask = DecodeFieldMask(context, mutation.update_mask); return PatchMutation(std::move(key), std::move(value), std::move(mask), @@ -743,17 +506,9 @@ Precondition Serializer::DecodePrecondition( google_firestore_v1_DocumentMask Serializer::EncodeFieldMask( const FieldMask& mask) { google_firestore_v1_DocumentMask result{}; - - pb_size_t count = CheckedSize(mask.size()); - result.field_paths_count = count; - result.field_paths = MakeArray(count); - - int i = 0; - for (const FieldPath& path : mask) { - result.field_paths[i] = EncodeFieldPath(path); - i++; - } - + SetRepeatedField( + &result.field_paths, &result.field_paths_count, mask, + [&](const FieldPath& path) { return EncodeFieldPath(path); }); return result; } @@ -785,15 +540,21 @@ Serializer::EncodeFieldTransform(const FieldTransform& field_transform) const { case Type::ArrayUnion: proto.which_transform_type = google_firestore_v1_DocumentTransform_FieldTransform_append_missing_elements_tag; // NOLINT - proto.append_missing_elements = EncodeArray( - ArrayTransform(field_transform.transformation()).elements()); + // TODO(mrschmidt): Figure out how to remove this copy + proto.append_missing_elements = + *DeepClone( + ArrayTransform(field_transform.transformation()).elements()) + .release(); return proto; case Type::ArrayRemove: proto.which_transform_type = google_firestore_v1_DocumentTransform_FieldTransform_remove_all_from_array_tag; // NOLINT - proto.remove_all_from_array = EncodeArray( - ArrayTransform(field_transform.transformation()).elements()); + // TODO(mrschmidt): Figure out how to remove this copy + proto.remove_all_from_array = + *DeepClone( + ArrayTransform(field_transform.transformation()).elements()) + .release(); return proto; case Type::Increment: { @@ -801,7 +562,7 @@ Serializer::EncodeFieldTransform(const FieldTransform& field_transform) const { google_firestore_v1_DocumentTransform_FieldTransform_increment_tag; const auto& increment = static_cast( field_transform.transformation()); - proto.increment = EncodeFieldValue(increment.operand()); + proto.increment = increment.operand(); return proto; } } @@ -811,7 +572,7 @@ Serializer::EncodeFieldTransform(const FieldTransform& field_transform) const { FieldTransform Serializer::DecodeFieldTransform( ReadContext* context, - const google_firestore_v1_DocumentTransform_FieldTransform& proto) const { + google_firestore_v1_DocumentTransform_FieldTransform& proto) const { FieldPath field = DecodeFieldPath(context, proto.field_path); switch (proto.which_transform_type) { @@ -825,26 +586,31 @@ FieldTransform Serializer::DecodeFieldTransform( } case google_firestore_v1_DocumentTransform_FieldTransform_append_missing_elements_tag: { // NOLINT - std::vector elements = - DecodeArray(context, proto.append_missing_elements); - return FieldTransform(std::move(field), - ArrayTransform(TransformOperation::Type::ArrayUnion, - std::move(elements))); + FieldTransform field_transform( + std::move(field), + ArrayTransform(TransformOperation::Type::ArrayUnion, + MakeMessage(proto.append_missing_elements))); + // Release field ownership to prevent double-freeing. The values are now + // owned by the FieldTransform. + proto.append_missing_elements = {}; + return field_transform; } case google_firestore_v1_DocumentTransform_FieldTransform_remove_all_from_array_tag: { // NOLINT - std::vector elements = - DecodeArray(context, proto.remove_all_from_array); - return FieldTransform( + FieldTransform field_transform( std::move(field), ArrayTransform(TransformOperation::Type::ArrayRemove, - std::move(elements))); + MakeMessage(proto.remove_all_from_array))); + // Release field ownership to prevent double-freeing. The values are now + // owned by the FieldTransform. + proto.append_missing_elements = {}; + return field_transform; } case google_firestore_v1_DocumentTransform_FieldTransform_increment_tag: { - FieldValue operand = DecodeFieldValue(context, proto.increment); - return FieldTransform(std::move(field), - NumericIncrementTransform(std::move(operand))); + return FieldTransform( + std::move(field), + NumericIncrementTransform(MakeMessage(proto.increment))); } } @@ -963,7 +729,7 @@ google_firestore_v1_Target_QueryTarget Serializer::EncodeQueryTarget( Target Serializer::DecodeStructuredQuery( ReadContext* context, pb_bytes_array_t* parent, - const google_firestore_v1_StructuredQuery& query) const { + google_firestore_v1_StructuredQuery& query) const { ResourcePath path = DecodeQueryPath(context, DecodeString(parent)); CollectionGroupId collection_group; @@ -1001,14 +767,14 @@ Target Serializer::DecodeStructuredQuery( limit = query.limit.value; } - std::shared_ptr start_at; + absl::optional start_at; if (query.start_at.values_count > 0) { - start_at = DecodeBound(context, query.start_at); + start_at = DecodeBound(query.start_at); } - std::shared_ptr end_at; + absl::optional end_at; if (query.end_at.values_count > 0) { - end_at = DecodeBound(context, query.end_at); + end_at = DecodeBound(query.end_at); } return Target(std::move(path), std::move(collection_group), @@ -1017,8 +783,7 @@ Target Serializer::DecodeStructuredQuery( } Target Serializer::DecodeQueryTarget( - ReadContext* context, - const google_firestore_v1_Target_QueryTarget& query) const { + ReadContext* context, google_firestore_v1_Target_QueryTarget& query) const { // The QueryTarget oneof only has a single valid value. if (query.which_query_type != google_firestore_v1_Target_QueryTarget_structured_query_tag) { @@ -1051,25 +816,16 @@ google_firestore_v1_StructuredQuery_Filter Serializer::EncodeFilters( composite.op = google_firestore_v1_StructuredQuery_CompositeFilter_Operator_AND; - auto count = CheckedSize(filters_count); - composite.filters_count = count; - composite.filters = - MakeArray(count); - pb_size_t i = 0; - for (const auto& filter : filters) { - if (filter.IsAFieldFilter()) { - HARD_ASSERT(i < count, "Index out of bounds"); - composite.filters[i] = EncodeSingularFilter(FieldFilter{filter}); - ++i; - } - } + SetRepeatedField( + &composite.filters, &composite.filters_count, filters, + [&](const Filter& f) { return EncodeSingularFilter(FieldFilter{f}); }); return result; } FilterList Serializer::DecodeFilters( ReadContext* context, - const google_firestore_v1_StructuredQuery_Filter& proto) const { + google_firestore_v1_StructuredQuery_Filter& proto) const { FilterList result; switch (proto.which_filter_type) { @@ -1095,7 +851,7 @@ google_firestore_v1_StructuredQuery_Filter Serializer::EncodeSingularFilter( bool is_unary = (filter.op() == Filter::Operator::Equal || filter.op() == Filter::Operator::NotEqual) && - (filter.value().is_nan() || filter.value().is_null()); + (IsNaNValue(filter.value()) || IsNullValue(filter.value())); if (is_unary) { result.which_filter_type = google_firestore_v1_StructuredQuery_Filter_unary_filter_tag; @@ -1104,13 +860,13 @@ google_firestore_v1_StructuredQuery_Filter Serializer::EncodeSingularFilter( result.unary_filter.field.field_path = EncodeFieldPath(filter.field()); bool is_equality = filter.op() == Filter::Operator::Equal; - if (filter.value().is_nan()) { + if (IsNaNValue(filter.value())) { result.unary_filter.op = is_equality ? google_firestore_v1_StructuredQuery_UnaryFilter_Operator_IS_NAN : google_firestore_v1_StructuredQuery_UnaryFilter_Operator_IS_NOT_NAN; // NOLINT - } else if (filter.value().is_null()) { + } else if (IsNullValue(filter.value())) { result.unary_filter.op = is_equality ? google_firestore_v1_StructuredQuery_UnaryFilter_Operator_IS_NULL @@ -1128,20 +884,22 @@ google_firestore_v1_StructuredQuery_Filter Serializer::EncodeSingularFilter( result.field_filter.field.field_path = EncodeFieldPath(filter.field()); result.field_filter.op = EncodeFieldFilterOperator(filter.op()); - result.field_filter.value = EncodeFieldValue(filter.value()); + // TODO(mrschmidt): Figure out how to remove this copy + result.field_filter.value = *DeepClone(filter.value()).release(); return result; } Filter Serializer::DecodeFieldFilter( ReadContext* context, - const google_firestore_v1_StructuredQuery_FieldFilter& field_filter) const { + google_firestore_v1_StructuredQuery_FieldFilter& field_filter) const { FieldPath field_path = DecodeFieldPath(context, field_filter.field.field_path); Filter::Operator op = DecodeFieldFilterOperator(context, field_filter.op); - FieldValue value = DecodeFieldValue(context, field_filter.value); - - return FieldFilter::Create(std::move(field_path), op, std::move(value)); + Filter result = FieldFilter::Create(std::move(field_path), op, + MakeSharedMessage(field_filter.value)); + field_filter.value = {}; // Release field ownership + return result; } Filter Serializer::DecodeUnaryFilter( @@ -1156,20 +914,17 @@ Filter Serializer::DecodeUnaryFilter( switch (unary.op) { case google_firestore_v1_StructuredQuery_UnaryFilter_Operator_IS_NULL: - return FieldFilter::Create(std::move(field), Filter::Operator::Equal, - FieldValue::Null()); + return FieldFilter::Create(field, Filter::Operator::Equal, NullValue()); case google_firestore_v1_StructuredQuery_UnaryFilter_Operator_IS_NAN: - return FieldFilter::Create(std::move(field), Filter::Operator::Equal, - FieldValue::Nan()); + return FieldFilter::Create(field, Filter::Operator::Equal, NaNValue()); case google_firestore_v1_StructuredQuery_UnaryFilter_Operator_IS_NOT_NULL: - return FieldFilter::Create(std::move(field), Filter::Operator::NotEqual, - FieldValue::Null()); + return FieldFilter::Create(field, Filter::Operator::NotEqual, + NullValue()); case google_firestore_v1_StructuredQuery_UnaryFilter_Operator_IS_NOT_NAN: - return FieldFilter::Create(std::move(field), Filter::Operator::NotEqual, - FieldValue::Nan()); + return FieldFilter::Create(field, Filter::Operator::NotEqual, NaNValue()); default: context->Fail(StringFormat("Unrecognized UnaryFilter.op %s", unary.op)); @@ -1359,31 +1114,25 @@ OrderBy Serializer::DecodeOrderBy( google_firestore_v1_Cursor Serializer::EncodeBound(const Bound& bound) const { google_firestore_v1_Cursor result{}; result.before = bound.before(); - - auto count = CheckedSize(bound.position().size()); - result.values_count = count; - result.values = MakeArray(count); - - int i = 0; - for (const FieldValue& field_value : bound.position()) { - result.values[i] = EncodeFieldValue(field_value); - ++i; - } - + SetRepeatedField( + &result.values, &result.values_count, + absl::Span(bound.position()->values, + bound.position()->values_count), + [](const google_firestore_v1_Value& value) { + return *DeepClone(value).release(); + }); return result; } -std::shared_ptr Serializer::DecodeBound( - ReadContext* context, const google_firestore_v1_Cursor& cursor) const { - std::vector index_components; - index_components.reserve(cursor.values_count); - - for (pb_size_t i = 0; i != cursor.values_count; ++i) { - FieldValue value = DecodeFieldValue(context, cursor.values[i]); - index_components.push_back(std::move(value)); - } - - return std::make_shared(std::move(index_components), cursor.before); +Bound Serializer::DecodeBound(google_firestore_v1_Cursor& cursor) const { + SharedMessage index_components{{}}; + SetRepeatedField(&index_components->values, &index_components->values_count, + absl::Span(cursor.values, + cursor.values_count)); + // Prevent double-freeing of the cursors's fields. The fields are now owned by + // the bound. + ReleaseFieldOwnership(cursor.values, cursor.values_count); + return Bound::FromValue(std::move(index_components), cursor.before); } /* static */ @@ -1435,101 +1184,9 @@ Timestamp Serializer::DecodeTimestamp( return decoded.ConsumeValueOrDie(); } -FieldValue Serializer::DecodeReference( - ReadContext* context, const pb_bytes_array_t* resource_name_raw) const { - return DecodeReference(context, MakeStringView(resource_name_raw)); -} - -FieldValue Serializer::DecodeReference( - ReadContext* context, absl::string_view reference_value) const { - ResourcePath resource_name = DecodeResourceName(context, reference_value); - ValidateDocumentKeyPath(context, resource_name); - DatabaseId database_id = DecodeDatabaseId(context, resource_name); - if (!context->status().ok()) { - return FieldValue::Null(); - } - - DocumentKey key = DecodeKey(context, resource_name); - - return FieldValue::FromReference(std::move(database_id), std::move(key)); -} - -/* static */ -GeoPoint Serializer::DecodeGeoPoint(ReadContext* context, - const google_type_LatLng& latlng_proto) { - // The GeoPoint ctor will assert if we provide values outside the valid range. - // However, since we're decoding, a single corrupt byte could cause this to - // occur, so we'll verify the ranges before passing them in since we'd rather - // not abort in these situations. - double latitude = latlng_proto.latitude; - double longitude = latlng_proto.longitude; - if (std::isnan(latitude) || latitude < -90 || 90 < latitude) { - context->Fail( - "Invalid message: Latitude must be in the range of [-90, 90]"); - } else if (std::isnan(longitude) || longitude < -180 || 180 < longitude) { - context->Fail( - "Invalid message: Latitude must be in the range of [-180, 180]"); - } - - if (!context->status().ok()) return GeoPoint(); - return GeoPoint(latitude, longitude); -} - -google_firestore_v1_ArrayValue Serializer::EncodeArray( - const std::vector& array_value) const { - google_firestore_v1_ArrayValue result{}; - - pb_size_t count = CheckedSize(array_value.size()); - result.values_count = count; - result.values = MakeArray(count); - - size_t i = 0; - for (const FieldValue& fv : array_value) { - result.values[i++] = EncodeFieldValue(fv); - } - - return result; -} - -std::vector Serializer::DecodeArray( - ReadContext* context, - const google_firestore_v1_ArrayValue& array_proto) const { - std::vector result; - result.reserve(array_proto.values_count); - - for (size_t i = 0; i < array_proto.values_count; i++) { - FieldValue field_value = DecodeFieldValue(context, array_proto.values[i]); - if (!context->status().ok()) { - return std::vector{}; - } - result.push_back(field_value); - } - - return result; -} - -google_firestore_v1_MapValue Serializer::EncodeMapValue( - const ObjectValue& object_value) const { - google_firestore_v1_MapValue result{}; - - pb_size_t count = CheckedSize(object_value.GetInternalValue().size()); - - result.fields_count = count; - result.fields = MakeArray(count); - - int i = 0; - for (const auto& kv : object_value.GetInternalValue()) { - result.fields[i].key = EncodeString(kv.first); - result.fields[i].value = EncodeFieldValue(kv.second); - i++; - } - - return result; -} - MutationResult Serializer::DecodeMutationResult( ReadContext* context, - const google_firestore_v1_WriteResult& write_result, + google_firestore_v1_WriteResult& write_result, const SnapshotVersion& commit_version) const { // NOTE: Deletes don't have an update_time, use commit_version instead. SnapshotVersion version = @@ -1537,15 +1194,15 @@ MutationResult Serializer::DecodeMutationResult( ? DecodeVersion(context, write_result.update_time) : commit_version; - absl::optional> transform_results; - if (write_result.transform_results_count > 0) { - transform_results = std::vector{}; - for (pb_size_t i = 0; i < write_result.transform_results_count; i++) { - transform_results->push_back( - DecodeFieldValue(context, write_result.transform_results[i])); - } - } - + Message transform_results; + SetRepeatedField(&transform_results->values, &transform_results->values_count, + absl::Span( + write_result.transform_results, + write_result.transform_results_count)); + // Prevent double-freeing of the transform result. The fields are now owned by + // the mutation result. + ReleaseFieldOwnership(write_result.transform_results, + write_result.transform_results_count); return MutationResult(version, std::move(transform_results)); } @@ -1577,7 +1234,7 @@ std::string Serializer::EncodeLabel(QueryPurpose purpose) const { std::unique_ptr Serializer::DecodeWatchChange( ReadContext* context, - const google_firestore_v1_ListenResponse& watch_change) const { + google_firestore_v1_ListenResponse& watch_change) const { switch (watch_change.which_response_type) { case google_firestore_v1_ListenResponse_target_change_tag: return DecodeTargetChange(context, watch_change.target_change); @@ -1659,10 +1316,9 @@ WatchTargetChangeState Serializer::DecodeTargetChangeState( } std::unique_ptr Serializer::DecodeDocumentChange( - ReadContext* context, - const google_firestore_v1_DocumentChange& change) const { - ObjectValue value = DecodeFields(context, change.document.fields_count, - change.document.fields); + ReadContext* context, google_firestore_v1_DocumentChange& change) const { + ObjectValue value = ObjectValue::FromFieldsEntry( + change.document.fields, change.document.fields_count); DocumentKey key = DecodeKey(context, change.document.name); HARD_ASSERT(change.document.has_update_time, @@ -1675,7 +1331,8 @@ std::unique_ptr Serializer::DecodeDocumentChange( // would defeat the purpose). Note, however, that even without this // optimization C++ implementation is on par with the preceding Objective-C // implementation. - Document document(std::move(value), key, version, DocumentState::kSynced); + MutableDocument document = + MutableDocument::FoundDocument(key, version, std::move(value)); std::vector updated_target_ids( change.target_ids, change.target_ids + change.target_ids_count); @@ -1697,7 +1354,7 @@ std::unique_ptr Serializer::DecodeDocumentDelete( SnapshotVersion version = change.has_read_time ? DecodeVersion(context, change.read_time) : SnapshotVersion::None(); - NoDocument document(key, version, /* has_committed_mutations= */ false); + MutableDocument document = MutableDocument::NoDocument(key, version); std::vector removed_target_ids( change.removed_target_ids, @@ -1733,6 +1390,12 @@ bool Serializer::IsLocalResourceName(const ResourcePath& path) const { path[3] == database_id_.database_id(); } +bool Serializer::IsLocalDocumentKey(absl::string_view path) const { + auto resource = ResourcePath::FromStringView(path); + return IsLocalResourceName(resource) && + DocumentKey::IsDocumentKey(resource.PopFirst(5)); +} + } // namespace remote } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/remote/serializer.h b/Firestore/core/src/remote/serializer.h index cc05ce4831a..5dd7350003e 100644 --- a/Firestore/core/src/remote/serializer.h +++ b/Firestore/core/src/remote/serializer.h @@ -31,7 +31,6 @@ #include "Firestore/core/src/core/core_fwd.h" #include "Firestore/core/src/core/filter.h" #include "Firestore/core/src/model/database_id.h" -#include "Firestore/core/src/model/field_value.h" #include "Firestore/core/src/model/model_fwd.h" #include "Firestore/core/src/model/resource_path.h" #include "Firestore/core/src/nanopb/byte_string.h" @@ -100,37 +99,12 @@ class Serializer { */ static std::string DecodeString(const pb_bytes_array_t* str); - /** - * Encodes the std::vector to nanopb bytes. If the input vector is empty, then - * the resulting return bytes will have length 0 (but will otherwise be valid, - * i.e. not null.) - * - * This method allocates memory; the caller is responsible for freeing it. - * Typically, the returned value will be added to a pointer field within a - * nanopb proto struct. Calling pb_release() on the resulting struct will - * cause all proto fields to be freed. - */ - static pb_bytes_array_t* EncodeBytes(const std::vector& bytes); - /** * Returns the database ID, such as * `projects/{project_id}/databases/{database_id}`. */ pb_bytes_array_t* EncodeDatabaseName() const; - /** - * @brief Converts the FieldValue model passed into bytes. - */ - google_firestore_v1_Value EncodeFieldValue( - const model::FieldValue& field_value) const; - - /** - * @brief Converts from nanopb proto to the model FieldValue format. - */ - // TODO(wuandy): all `context` here should be mutable reference instead. - model::FieldValue DecodeFieldValue( - util::ReadContext* context, const google_firestore_v1_Value& msg) const; - /** * Encodes the given document key as a fully qualified name. This includes the * DatabaseId associated with this Serializer and the key path. @@ -153,15 +127,14 @@ class Serializer { /** * @brief Converts from nanopb proto to the model Document format. */ - model::MaybeDocument DecodeMaybeDocument( + model::MutableDocument DecodeMaybeDocument( util::ReadContext* context, - const google_firestore_v1_BatchGetDocumentsResponse& response) const; + google_firestore_v1_BatchGetDocumentsResponse& response) const; google_firestore_v1_Write EncodeMutation( const model::Mutation& mutation) const; - model::Mutation DecodeMutation( - util::ReadContext* context, - const google_firestore_v1_Write& mutation) const; + model::Mutation DecodeMutation(util::ReadContext* context, + google_firestore_v1_Write& mutation) const; static google_firestore_v1_Precondition EncodePrecondition( const model::Precondition& precondition); @@ -178,11 +151,11 @@ class Serializer { const model::FieldTransform& field_transform) const; model::FieldTransform DecodeFieldTransform( util::ReadContext* context, - const google_firestore_v1_DocumentTransform_FieldTransform& proto) const; + google_firestore_v1_DocumentTransform_FieldTransform& proto) const; model::MutationResult DecodeMutationResult( util::ReadContext* context, - const google_firestore_v1_WriteResult& write_result, + google_firestore_v1_WriteResult& write_result, const model::SnapshotVersion& commit_version) const; std::vector @@ -205,18 +178,6 @@ class Serializer { util::ReadContext* context, const google_protobuf_Timestamp& timestamp_proto); - static GeoPoint DecodeGeoPoint(util::ReadContext* context, - const google_type_LatLng& latlng_proto); - - google_firestore_v1_ArrayValue EncodeArray( - const std::vector& array_value) const; - std::vector DecodeArray( - util::ReadContext* context, - const google_firestore_v1_ArrayValue& array_proto) const; - - google_firestore_v1_MapValue EncodeMapValue( - const model::ObjectValue& object_value) const; - google_firestore_v1_Target EncodeTarget( const local::TargetData& target_data) const; google_firestore_v1_Target_DocumentsTarget EncodeDocumentsTarget( @@ -226,55 +187,62 @@ class Serializer { const google_firestore_v1_Target_DocumentsTarget& proto) const; google_firestore_v1_Target_QueryTarget EncodeQueryTarget( const core::Target& target) const; + + /** + * Decodes the query target. Modifies the provided proto to release ownership + * of any Value messages. + */ core::Target DecodeQueryTarget( util::ReadContext* context, - const google_firestore_v1_Target_QueryTarget& query) const; + google_firestore_v1_Target_QueryTarget& query) const; + core::Target DecodeStructuredQuery( util::ReadContext* context, pb_bytes_array_t* parent, - const google_firestore_v1_StructuredQuery& query) const; + google_firestore_v1_StructuredQuery& query) const; + /** + * Decodes the watch change. Modifies the provided proto to release + * ownership of any Value messages. + */ std::unique_ptr DecodeWatchChange( util::ReadContext* context, - const google_firestore_v1_ListenResponse& watch_change) const; + google_firestore_v1_ListenResponse& watch_change) const; model::SnapshotVersion DecodeVersionFromListenResponse( util::ReadContext* context, const google_firestore_v1_ListenResponse& listen_response) const; - model::ObjectValue DecodeFields( - util::ReadContext* context, - size_t count, - const google_firestore_v1_Document_FieldsEntry* fields) const; - // Public for the sake of tests. google_firestore_v1_StructuredQuery_Filter EncodeFilters( const core::FilterList& filters) const; + + /** + * Decodes the structured query. Modifies the provided proto to release + * ownership of any Value messages. + */ core::FilterList DecodeFilters( util::ReadContext* context, - const google_firestore_v1_StructuredQuery_Filter& proto) const; + google_firestore_v1_StructuredQuery_Filter& proto) const; + + /** + * Encodes a database ID and resource path into the following form: + * /projects/$project_id/database/$database_id/documents/$path + * + * Does not verify that the database_id matches the current instance. + */ + pb_bytes_array_t* EncodeResourceName(const model::DatabaseId& database_id, + const model::ResourcePath& path) const; bool IsLocalResourceName(const model::ResourcePath& path) const; - model::FieldValue DecodeReference(util::ReadContext* context, - absl::string_view reference_value) const; + bool IsLocalDocumentKey(absl::string_view path) const; private: - google_firestore_v1_Value EncodeNull() const; - google_firestore_v1_Value EncodeBoolean(bool value) const; - google_firestore_v1_Value EncodeInteger(int64_t value) const; - google_firestore_v1_Value EncodeDouble(double value) const; - google_firestore_v1_Value EncodeTimestampValue(Timestamp value) const; - google_firestore_v1_Value EncodeStringValue(const std::string& value) const; - google_firestore_v1_Value EncodeBlob(const nanopb::ByteString& value) const; - google_firestore_v1_Value EncodeReference( - const model::FieldValue::Reference& value) const; - google_firestore_v1_Value EncodeGeoPoint(const GeoPoint& value) const; - - model::Document DecodeFoundDocument( + model::MutableDocument DecodeFoundDocument( util::ReadContext* context, - const google_firestore_v1_BatchGetDocumentsResponse& response) const; - model::NoDocument DecodeMissingDocument( + google_firestore_v1_BatchGetDocumentsResponse& response) const; + model::MutableDocument DecodeMissingDocument( util::ReadContext* context, const google_firestore_v1_BatchGetDocumentsResponse& response) const; @@ -282,13 +250,6 @@ class Serializer { model::ResourcePath DecodeQueryPath(util::ReadContext* context, absl::string_view name) const; - /** - * Encodes a database ID and resource path into the following form: - * /projects/$project_id/database/$database_id/documents/$path - */ - pb_bytes_array_t* EncodeResourceName(const model::DatabaseId& database_id, - const model::ResourcePath& path) const; - /** * 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 @@ -302,29 +263,13 @@ class Serializer { model::DocumentKey DecodeKey(util::ReadContext* context, const model::ResourcePath& resource_name) const; - model::FieldValue::Map::value_type DecodeFieldsEntry( - util::ReadContext* context, - const google_firestore_v1_Document_FieldsEntry& fields) const; - - model::FieldValue::Map DecodeMapValue( - util::ReadContext* context, - const google_firestore_v1_MapValue& map_value) const; - - model::DatabaseId DecodeDatabaseId( - util::ReadContext* context, - const model::ResourcePath& resource_name) const; - model::FieldValue DecodeReference( - util::ReadContext* context, - const pb_bytes_array_t* resource_name_raw) const; - std::string EncodeLabel(local::QueryPurpose purpose) const; google_firestore_v1_StructuredQuery_Filter EncodeSingularFilter( const core::FieldFilter& filter) const; core::Filter DecodeFieldFilter( util::ReadContext* context, - const google_firestore_v1_StructuredQuery_FieldFilter& field_filter) - const; + google_firestore_v1_StructuredQuery_FieldFilter& field_filter) const; core::Filter DecodeUnaryFilter( util::ReadContext* context, const google_firestore_v1_StructuredQuery_UnaryFilter& unary) const; @@ -350,9 +295,7 @@ class Serializer { const google_firestore_v1_StructuredQuery_Order& order_by) const; google_firestore_v1_Cursor EncodeBound(const core::Bound& bound) const; - std::shared_ptr DecodeBound( - util::ReadContext* context, - const google_firestore_v1_Cursor& cursor) const; + core::Bound DecodeBound(google_firestore_v1_Cursor& cursor) const; std::unique_ptr DecodeTargetChange( util::ReadContext* context, @@ -363,7 +306,7 @@ class Serializer { std::unique_ptr DecodeDocumentChange( util::ReadContext* context, - const google_firestore_v1_DocumentChange& change) const; + google_firestore_v1_DocumentChange& change) const; std::unique_ptr DecodeDocumentDelete( util::ReadContext* context, const google_firestore_v1_DocumentDelete& change) const; diff --git a/Firestore/core/src/remote/watch_change.h b/Firestore/core/src/remote/watch_change.h index 9061c20bfc1..ba2951c66c2 100644 --- a/Firestore/core/src/remote/watch_change.h +++ b/Firestore/core/src/remote/watch_change.h @@ -21,7 +21,7 @@ #include #include "Firestore/core/src/model/document_key.h" -#include "Firestore/core/src/model/maybe_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/types.h" #include "Firestore/core/src/nanopb/byte_string.h" #include "Firestore/core/src/remote/existence_filter.h" @@ -65,7 +65,7 @@ class DocumentWatchChange : public WatchChange { DocumentWatchChange(std::vector updated_target_ids, std::vector removed_target_ids, model::DocumentKey document_key, - absl::optional new_document) + absl::optional new_document) : updated_target_ids_{std::move(updated_target_ids)}, removed_target_ids_{std::move(removed_target_ids)}, document_key_{std::move(document_key)}, @@ -90,7 +90,7 @@ class DocumentWatchChange : public WatchChange { * The new document, or `DeletedDocument` if it was deleted. Is null if the * document went out of view without the server sending a new document. */ - const absl::optional& new_document() const { + const absl::optional& new_document() const { return new_document_; } @@ -103,7 +103,7 @@ class DocumentWatchChange : public WatchChange { std::vector updated_target_ids_; std::vector removed_target_ids_; model::DocumentKey document_key_; - absl::optional new_document_; + absl::optional new_document_; }; bool operator==(const DocumentWatchChange& lhs, const DocumentWatchChange& rhs); diff --git a/Firestore/core/src/remote/write_stream.cc b/Firestore/core/src/remote/write_stream.cc index 1a18390050c..3b87ddeda7a 100644 --- a/Firestore/core/src/remote/write_stream.cc +++ b/Firestore/core/src/remote/write_stream.cc @@ -146,7 +146,7 @@ Status WriteStream::NotifyStreamResponse(const grpc::ByteBuffer& message) { return reader.status(); } - callback_->OnWriteStreamMutationResult(version, results); + callback_->OnWriteStreamMutationResult(version, std::move(results)); } return Status::OK(); diff --git a/Firestore/core/src/util/comparison.cc b/Firestore/core/src/util/comparison.cc index 15fd788708d..296d5c44c3a 100644 --- a/Firestore/core/src/util/comparison.cc +++ b/Firestore/core/src/util/comparison.cc @@ -29,18 +29,6 @@ namespace util { using std::isnan; -/** - * Creates a ComparisonResult from a typical integer return value, where - * 0 means "same", less than zero means "ascending", and greater than zero - * means "descending". - */ -constexpr ComparisonResult ComparisonResultFromInt(int value) { - // TODO(c++14): convert this to an if statement. - return value < 0 ? ComparisonResult::Ascending - : (value > 0 ? ComparisonResult::Descending - : ComparisonResult::Same); -} - ComparisonResult Comparator::Compare( absl::string_view left, absl::string_view right) const { return ComparisonResultFromInt(left.compare(right)); diff --git a/Firestore/core/src/util/comparison.h b/Firestore/core/src/util/comparison.h index 516cffe113a..01092e55c5c 100644 --- a/Firestore/core/src/util/comparison.h +++ b/Firestore/core/src/util/comparison.h @@ -72,6 +72,18 @@ constexpr bool Descending(ComparisonResult result) noexcept { return result == ComparisonResult::Descending; } +/** + * Creates a ComparisonResult from a typical integer return value, where + * 0 means "same", less than zero means "ascending", and greater than zero + * means "descending". + */ +constexpr ComparisonResult ComparisonResultFromInt(int value) { + // TODO(c++14): convert this to an if statement. + return value < 0 ? ComparisonResult::Ascending + : (value > 0 ? ComparisonResult::Descending + : ComparisonResult::Same); +} + /** * Returns the reverse order (i.e. Ascending => Descending) etc. */ diff --git a/Firestore/core/test/unit/bundle/bundle_loader_test.cc b/Firestore/core/test/unit/bundle/bundle_loader_test.cc index 6b1836450e9..8df3a26e5ca 100644 --- a/Firestore/core/test/unit/bundle/bundle_loader_test.cc +++ b/Firestore/core/test/unit/bundle/bundle_loader_test.cc @@ -43,7 +43,7 @@ using api::LoadBundleTaskProgress; using api::LoadBundleTaskState; using core::LimitType; using model::DocumentKeySet; -using model::MaybeDocumentMap; +using model::DocumentMap; using util::StatusOr; class BundleLoaderTest : public ::testing::Test { @@ -53,14 +53,14 @@ class BundleLoaderTest : public ::testing::Test { explicit TestBundleCallback(BundleLoaderTest& parant) : parent_(parant) { } - model::MaybeDocumentMap ApplyBundledDocuments( - const model::MaybeDocumentMap& documents, + model::DocumentMap ApplyBundledDocuments( + const model::MutableDocumentMap& documents, const std::string& bundle_id) override { (void)bundle_id; for (const auto& entry : documents) { parent_.last_documents_ = parent_.last_documents_.insert(entry.first); } - return MaybeDocumentMap{}; + return DocumentMap{}; } void SaveNamedQuery(const NamedQuery& query, diff --git a/Firestore/core/test/unit/bundle/bundle_serializer_test.cc b/Firestore/core/test/unit/bundle/bundle_serializer_test.cc index 99df5bfac6b..47bdafb18f5 100644 --- a/Firestore/core/test/unit/bundle/bundle_serializer_test.cc +++ b/Firestore/core/test/unit/bundle/bundle_serializer_test.cc @@ -24,6 +24,7 @@ #include "Firestore/core/src/core/target.h" #include "Firestore/core/src/local/local_serializer.h" #include "Firestore/core/src/model/database_id.h" +#include "Firestore/core/src/model/value_util.h" #include "Firestore/core/src/nanopb/byte_string.h" #include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/remote/serializer.h" @@ -55,6 +56,7 @@ using local::LocalSerializer; using model::DatabaseId; using nanopb::ByteString; using nanopb::MakeByteString; +using nanopb::MakeSharedMessage; using nanopb::ProtobufParse; using remote::Serializer; using std::numeric_limits; @@ -113,8 +115,8 @@ class BundleSerializerTest : public ::testing::Test { VerifyDecodedDocumentEncodesToOriginal(actual.document(), document); } - void VerifyDecodedDocumentEncodesToOriginal(const model::Document& decoded, - const ProtoDocument& original) { + void VerifyDecodedDocumentEncodesToOriginal( + const model::MutableDocument& decoded, const ProtoDocument& original) { ByteString bytes = nanopb::MakeByteString(local_serializer.EncodeMaybeDocument(decoded)); ProtoMaybeDocument maybe_document; @@ -443,7 +445,7 @@ TEST_F(BundleSerializerTest, DecodesInvalidDoubleValueFails) { TEST_F(BundleSerializerTest, DecodesNanDoubleValues) { ProtoValue value; - value.set_double_value(absl::bit_cast(model::kCanonicalNanBits)); + value.set_double_value(absl::bit_cast(testutil::kCanonicalNanBits)); ProtoDocument document = TestDocument(value); std::string json_string; @@ -453,9 +455,9 @@ TEST_F(BundleSerializerTest, DecodesNanDoubleValues) { BundleDocument actual = bundle_serializer.DecodeDocument(reader, Parse(json_string)); EXPECT_OK(reader.status()); - auto actual_value = actual.document().data().Get( - model::FieldPath::FromDotSeparatedString("foo")); - EXPECT_TRUE(actual_value->is_nan()); + auto actual_value = + actual.document().field(model::FieldPath::FromDotSeparatedString("foo")); + EXPECT_TRUE(model::IsNaNValue(*actual_value)); } TEST_F(BundleSerializerTest, DecodesStrings) { @@ -598,29 +600,6 @@ TEST_F(BundleSerializerTest, DecodesReferenceValues) { VerifyFieldValueRoundtrip(value); } -TEST_F(BundleSerializerTest, DecodeReferenceValuesFromOtherProjectsFails) { - ProtoValue value; - value.set_reference_value( - "projects/p1/databases/new/documents/bundle/test_doc"); - ProtoDocument document = TestDocument(value); - - std::string json_string; - MessageToJsonString(document, &json_string); - - VerifyJsonStringDecodeFails(json_string); -} - -TEST_F(BundleSerializerTest, DecodeInvalidReferenceFails) { - ProtoValue value; - value.set_reference_value("projectxx/p1/datmm/new/documents/bundle/test_doc"); - ProtoDocument document = TestDocument(value); - - std::string json_string; - MessageToJsonString(document, &json_string); - - VerifyJsonStringDecodeFails(json_string); -} - TEST_F(BundleSerializerTest, DecodesArrayValues) { ProtoValue elem1; elem1.set_string_value("testing"); @@ -819,7 +798,7 @@ TEST_F(BundleSerializerTest, DecodesArrayContainsFilter) { TEST_F(BundleSerializerTest, DecodesInFilter) { core::Query original = testutil::CollectionGroupQuery("colls").AddingFilter( - Filter("f1", "in", Array("f", "h"))); + Filter("f1", "in", Value(Array("f", "h")))); VerifyNamedQueryRoundtrip(original); } @@ -1030,19 +1009,21 @@ TEST_F(BundleSerializerTest, DecodeInvalidLimitQueriesFails) { } TEST_F(BundleSerializerTest, DecodesStartAtCursor) { - core::Query original = testutil::Query("colls") - .AddingOrderBy(OrderBy("f1", "asc")) - .StartingAt(core::Bound({Value("f1"), Value(1000)}, - /* is_before= */ true)); + core::Query original = + testutil::Query("colls") + .AddingOrderBy(OrderBy("f1", "asc")) + .StartingAt(core::Bound::FromValue(Array("f1", 1000), + /* is_before= */ true)); VerifyNamedQueryRoundtrip(original); } TEST_F(BundleSerializerTest, DecodesEndAtCursor) { - core::Query original = testutil::Query("colls") - .AddingOrderBy(OrderBy("f1", "desc")) - .EndingAt(core::Bound({Value("f1"), Value("1000")}, - /* is_before= */ false)); + core::Query original = + testutil::Query("colls") + .AddingOrderBy(OrderBy("f1", "desc")) + .EndingAt(core::Bound::FromValue(Array("f1", "1000"), + /* is_before= */ false)); VerifyNamedQueryRoundtrip(original); } @@ -1051,8 +1032,8 @@ TEST_F(BundleSerializerTest, DecodeInvalidCursorQueriesFails) { std::string json_string = NamedQueryJsonString( testutil::Query("colls") .AddingOrderBy(OrderBy("f1", "desc")) - .EndingAt(core::Bound({Value("f1"), Value("1000")}, - /* is_before= */ false))); + .EndingAt(core::Bound::FromValue(Array("f1", "1000"), + /* is_before= */ false))); auto json_copy = ReplacedCopy(json_string, "\"1000\"", "[]"); auto reader = JsonReader(); bundle_serializer.DecodeNamedQuery(reader, Parse(json_copy)); diff --git a/Firestore/core/test/unit/core/query_listener_test.cc b/Firestore/core/test/unit/core/query_listener_test.cc index 6a0572a0202..9df38052afe 100644 --- a/Firestore/core/test/unit/core/query_listener_test.cc +++ b/Firestore/core/test/unit/core/query_listener_test.cc @@ -43,10 +43,9 @@ namespace firebase { namespace firestore { namespace core { -using model::Document; using model::DocumentKeySet; using model::DocumentSet; -using model::DocumentState; +using model::MutableDocument; using model::OnlineState; using remote::TargetChange; using util::DelayedConstructor; @@ -103,9 +102,9 @@ TEST_F(QueryListenerTest, RaisesCollectionEvents) { std::vector other_accum; Query query = testutil::Query("rooms"); - Document doc1 = Doc("rooms/Eros", 1, Map("name", "Eros")); - Document doc2 = Doc("rooms/Hades", 2, Map("name", "Hades")); - Document doc2prime = + MutableDocument doc1 = Doc("rooms/Eros", 1, Map("name", "Eros")); + MutableDocument doc2 = Doc("rooms/Hades", 2, Map("name", "Hades")); + MutableDocument doc2prime = Doc("rooms/Hades", 3, Map("name", "Hades", "owner", "Jonny")); auto listener = QueryListener::Create(query, include_metadata_changes_, @@ -180,8 +179,8 @@ TEST_F(QueryListenerTest, MutingAsyncListenerPreventsAllSubsequentEvents) { std::vector accum; Query query = testutil::Query("rooms/Eros"); - Document doc1 = Doc("rooms/Eros", 3, Map("name", "Eros")); - Document doc2 = Doc("rooms/Eros", 4, Map("name", "Eros2")); + MutableDocument doc1 = Doc("rooms/Eros", 3, Map("name", "Eros")); + MutableDocument doc2 = Doc("rooms/Eros", 4, Map("name", "Eros2")); std::shared_ptr> listener = AsyncEventListener::Create( @@ -215,8 +214,8 @@ TEST_F(QueryListenerTest, DoesNotRaiseEventsForMetadataChangesUnlessSpecified) { std::vector full_accum; Query query = testutil::Query("rooms"); - Document doc1 = Doc("rooms/Eros", 1, Map("name", "Eros")); - Document doc2 = Doc("rooms/Hades", 2, Map("name", "Hades")); + MutableDocument doc1 = Doc("rooms/Eros", 1, Map("name", "Eros")); + MutableDocument doc2 = Doc("rooms/Hades", 2, Map("name", "Hades")); auto filtered_listener = QueryListener::Create(query, Accumulating(&filtered_accum)); @@ -248,11 +247,11 @@ TEST_F(QueryListenerTest, RaisesDocumentMetadataEventsOnlyWhenSpecified) { std::vector full_accum; Query query = testutil::Query("rooms"); - Document doc1 = - Doc("rooms/Eros", 1, Map("name", "Eros"), DocumentState::kLocalMutations); - Document doc2 = Doc("rooms/Hades", 2, Map("name", "Hades")); - Document doc1_prime = Doc("rooms/Eros", 1, Map("name", "Eros")); - Document doc3 = Doc("rooms/Other", 3, Map("name", "Other")); + MutableDocument doc1 = + Doc("rooms/Eros", 1, Map("name", "Eros")).SetHasLocalMutations(); + MutableDocument doc2 = Doc("rooms/Hades", 2, Map("name", "Hades")); + MutableDocument doc1_prime = Doc("rooms/Eros", 1, Map("name", "Eros")); + MutableDocument doc3 = Doc("rooms/Other", 3, Map("name", "Other")); ListenOptions options( /*include_query_metadata_changes=*/false, @@ -298,13 +297,13 @@ TEST_F(QueryListenerTest, std::vector full_accum; Query query = testutil::Query("rooms"); - Document doc1 = - Doc("rooms/Eros", 1, Map("name", "Eros"), DocumentState::kLocalMutations); - Document doc2 = Doc("rooms/Hades", 2, Map("name", "Hades"), - DocumentState::kLocalMutations); - Document doc1_prime = Doc("rooms/Eros", 1, Map("name", "Eros")); - Document doc2_prime = Doc("rooms/Hades", 2, Map("name", "Hades")); - Document doc3 = Doc("rooms/Other", 3, Map("name", "Other")); + MutableDocument doc1 = + Doc("rooms/Eros", 1, Map("name", "Eros")).SetHasLocalMutations(); + MutableDocument doc2 = + Doc("rooms/Hades", 2, Map("name", "Hades")).SetHasLocalMutations(); + MutableDocument doc1_prime = Doc("rooms/Eros", 1, Map("name", "Eros")); + MutableDocument doc2_prime = Doc("rooms/Hades", 2, Map("name", "Hades")); + MutableDocument doc3 = Doc("rooms/Other", 3, Map("name", "Other")); ListenOptions options( /*include_query_metadata_changes=*/true, @@ -346,11 +345,11 @@ TEST_F(QueryListenerTest, std::vector filtered_accum; Query query = testutil::Query("rooms"); - Document doc1 = - Doc("rooms/Eros", 1, Map("name", "Eros"), DocumentState::kLocalMutations); - Document doc2 = Doc("rooms/Hades", 2, Map("name", "Hades")); - Document doc1_prime = Doc("rooms/Eros", 1, Map("name", "Eros")); - Document doc3 = Doc("rooms/Other", 3, Map("name", "Other")); + MutableDocument doc1 = + Doc("rooms/Eros", 1, Map("name", "Eros")).SetHasLocalMutations(); + MutableDocument doc2 = Doc("rooms/Hades", 2, Map("name", "Hades")); + MutableDocument doc1_prime = Doc("rooms/Eros", 1, Map("name", "Eros")); + MutableDocument doc3 = Doc("rooms/Other", 3, Map("name", "Other")); auto filtered_listener = QueryListener::Create(query, Accumulating(&filtered_accum)); @@ -381,8 +380,8 @@ TEST_F(QueryListenerTest, WillWaitForSyncIfOnline) { std::vector events; Query query = testutil::Query("rooms"); - Document doc1 = Doc("rooms/Eros", 1, Map("name", "Eros")); - Document doc2 = Doc("rooms/Hades", 2, Map("name", "Hades")); + MutableDocument doc1 = Doc("rooms/Eros", 1, Map("name", "Eros")); + MutableDocument doc2 = Doc("rooms/Hades", 2, Map("name", "Hades")); ListenOptions options( /*include_query_metadata_changes=*/false, @@ -420,8 +419,8 @@ TEST_F(QueryListenerTest, WillRaiseInitialEventWhenGoingOffline) { std::vector events; Query query = testutil::Query("rooms"); - Document doc1 = Doc("rooms/Eros", 1, Map("name", "Eros")); - Document doc2 = Doc("rooms/Hades", 2, Map("name", "Hades")); + MutableDocument doc1 = Doc("rooms/Eros", 1, Map("name", "Eros")); + MutableDocument doc2 = Doc("rooms/Hades", 2, Map("name", "Hades")); ListenOptions options( /*include_query_metadata_changes=*/false, diff --git a/Firestore/core/test/unit/core/query_test.cc b/Firestore/core/test/unit/core/query_test.cc index cdaffa54524..939c27e8dfc 100644 --- a/Firestore/core/test/unit/core/query_test.cc +++ b/Firestore/core/test/unit/core/query_test.cc @@ -20,11 +20,11 @@ #include "Firestore/core/src/core/bound.h" #include "Firestore/core/src/core/field_filter.h" -#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_set.h" #include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/resource_path.h" +#include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/test/unit/testutil/testutil.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -34,11 +34,11 @@ namespace firestore { namespace core { using firebase::firestore::util::ComparisonResult; -using model::Document; using model::DocumentComparator; using model::FieldPath; -using model::FieldValue; +using model::MutableDocument; using model::ResourcePath; +using nanopb::MakeSharedMessage; using testing::AssertionResult; using testing::Not; @@ -465,12 +465,13 @@ TEST(QueryTest, FiltersBasedOnObjectValue) { * Checks that an ordered array of elements yields the correct pair-wise * comparison result for the supplied comparator. */ -testing::AssertionResult CorrectComparisons(const std::vector& vector, - const DocumentComparator& comp) { +testing::AssertionResult CorrectComparisons( + const std::vector& vector, + const DocumentComparator& comp) { for (size_t i = 0; i < vector.size(); i++) { for (size_t j = 0; j < vector.size(); j++) { - const Document& i_doc = vector[i]; - const Document& j_doc = vector[j]; + const MutableDocument& i_doc = vector[i]; + const MutableDocument& j_doc = vector[j]; ComparisonResult expected = util::Compare(i, j); ComparisonResult actual = comp.Compare(i_doc, j_doc); if (actual != expected) { @@ -487,7 +488,7 @@ TEST(QueryTest, SortsDocumentsInTheCorrectOrder) { auto query = testutil::Query("collection").AddingOrderBy(OrderBy("sort")); // clang-format off - std::vector docs = { + std::vector docs = { Doc("collection/1", 0, Map("sort", nullptr)), Doc("collection/1", 0, Map("sort", false)), Doc("collection/1", 0, Map("sort", true)), @@ -514,7 +515,7 @@ TEST(QueryTest, SortsDocumentsUsingMultipleFields) { .AddingOrderBy(OrderBy("sort2")); // clang-format off - std::vector docs = { + std::vector docs = { Doc("collection/1", 0, Map("sort1", 1, "sort2", 1)), Doc("collection/1", 0, Map("sort1", 1, "sort2", 2)), Doc("collection/2", 0, Map("sort1", 1, "sort2", 2)), // by key @@ -537,7 +538,7 @@ TEST(QueryTest, SortsDocumentsWithDescendingToo) { .AddingOrderBy(OrderBy("sort2", "desc")); // clang-format off - std::vector docs = { + std::vector docs = { Doc("collection/1", 0, Map("sort1", 2, "sort2", 3)), Doc("collection/3", 0, Map("sort1", 2, "sort2", 2)), Doc("collection/2", 0, Map("sort1", 2, "sort2", 2)), // by key @@ -770,7 +771,7 @@ TEST(QueryTest, CanonicalIDs) { filters = testutil::Query("coll").AddingFilter( Filter("a", "not-in", Array(1, 2, 3))); EXPECT_THAT(filters, - HasCanonicalId("coll|f:anot-in[1, 2, 3]|ob:aasc__name__asc")); + HasCanonicalId("coll|f:anot-in[1,2,3]|ob:aasc__name__asc")); auto order_bys = testutil::Query("coll").AddingOrderBy(OrderBy("up", "asc")); EXPECT_THAT(order_bys, HasCanonicalId("coll|f:|ob:upasc__name__asc")); @@ -783,12 +784,13 @@ TEST(QueryTest, CanonicalIDs) { auto limit = testutil::Query("coll").WithLimitToFirst(25); EXPECT_THAT(limit, HasCanonicalId("coll|f:|ob:__name__asc|l:25|lt:f")); - auto bounds = - testutil::Query("airports") - .AddingOrderBy(OrderBy("name", "asc")) - .AddingOrderBy(OrderBy("score", "desc")) - .StartingAt(Bound({Value("OAK"), Value(1000)}, /* is_before= */ true)) - .EndingAt(Bound({Value("SFO"), Value(2000)}, /* is_before= */ false)); + auto bounds = testutil::Query("airports") + .AddingOrderBy(OrderBy("name", "asc")) + .AddingOrderBy(OrderBy("score", "desc")) + .StartingAt(Bound::FromValue(Array("OAK", 1000), + /* is_before= */ true)) + .EndingAt(Bound::FromValue(Array("SFO", 2000), + /* is_before= */ false)); EXPECT_THAT(bounds, HasCanonicalId("airports|f:|ob:nameascscoredesc__name__" "desc|lb:b:OAK1000|ub:a:SFO2000")); } @@ -809,10 +811,10 @@ TEST(QueryTest, MatchesAllDocuments) { query = base_query.WithLimitToFirst(1); EXPECT_FALSE(query.MatchesAllDocuments()); - query = base_query.StartingAt(Bound({Value("SFO")}, true)); + query = base_query.StartingAt(Bound::FromValue(Array("SFO"), true)); EXPECT_FALSE(query.MatchesAllDocuments()); - query = base_query.StartingAt(Bound({Value("OAK")}, true)); + query = base_query.StartingAt(Bound::FromValue(Array("OAK"), true)); EXPECT_FALSE(query.MatchesAllDocuments()); } diff --git a/Firestore/core/test/unit/core/view_snapshot_test.cc b/Firestore/core/test/unit/core/view_snapshot_test.cc index f0c0a04bfd9..d084d54ad8e 100644 --- a/Firestore/core/test/unit/core/view_snapshot_test.cc +++ b/Firestore/core/test/unit/core/view_snapshot_test.cc @@ -26,11 +26,10 @@ namespace firebase { namespace firestore { namespace core { -using model::Document; using model::DocumentComparator; using model::DocumentKeySet; using model::DocumentSet; -using model::DocumentState; +using model::MutableDocument; using testutil::Doc; using testutil::Map; @@ -38,7 +37,7 @@ using testutil::Map; using Type = DocumentViewChange::Type; TEST(ViewSnapshotTest, DocumentChangeConstructor) { - Document doc = Doc("a/b", 0, Map()); + MutableDocument doc = Doc("a/b", 0, Map()); Type type = Type::Modified; DocumentViewChange change{doc, type}; ASSERT_EQ(change.document(), doc); @@ -48,15 +47,15 @@ TEST(ViewSnapshotTest, DocumentChangeConstructor) { TEST(ViewSnapshotTest, Track) { DocumentViewChangeSet set; - Document doc_added = Doc("a/1", 0, Map()); - Document doc_removed = Doc("a/2", 0, Map()); - Document doc_modified = Doc("a/3", 0, Map()); + MutableDocument doc_added = Doc("a/1", 0, Map()); + MutableDocument doc_removed = Doc("a/2", 0, Map()); + MutableDocument doc_modified = Doc("a/3", 0, Map()); - Document doc_added_then_modified = Doc("b/1", 0, Map()); - Document doc_added_then_removed = Doc("b/2", 0, Map()); - Document doc_removed_then_added = Doc("b/3", 0, Map()); - Document doc_modified_then_removed = Doc("b/4", 0, Map()); - Document doc_modified_then_modified = Doc("b/5", 0, Map()); + MutableDocument doc_added_then_modified = Doc("b/1", 0, Map()); + MutableDocument doc_added_then_removed = Doc("b/2", 0, Map()); + MutableDocument doc_removed_then_added = Doc("b/3", 0, Map()); + MutableDocument doc_modified_then_removed = Doc("b/4", 0, Map()); + MutableDocument doc_modified_then_modified = Doc("b/5", 0, Map()); set.AddChange(DocumentViewChange{doc_added, Type::Added}); set.AddChange(DocumentViewChange{doc_removed, Type::Removed}); diff --git a/Firestore/core/test/unit/core/view_test.cc b/Firestore/core/test/unit/core/view_test.cc index 298d05941d4..7c4ac029b75 100644 --- a/Firestore/core/test/unit/core/view_test.cc +++ b/Firestore/core/test/unit/core/view_test.cc @@ -25,7 +25,6 @@ #include "Firestore/core/src/core/view_snapshot.h" #include "Firestore/core/src/model/document_key_set.h" #include "Firestore/core/src/model/document_set.h" -#include "Firestore/core/src/model/no_document.h" #include "Firestore/core/src/model/resource_path.h" #include "Firestore/core/test/unit/testutil/testutil.h" #include "Firestore/core/test/unit/testutil/view_testing.h" @@ -40,8 +39,6 @@ namespace core { using model::Document; using model::DocumentKeySet; using model::DocumentSet; -using model::DocumentState; -using model::FieldValue; using model::ResourcePath; using testing::ElementsAre; @@ -65,7 +62,7 @@ MATCHER_P(ContainsDocs, expected, "") { return false; } for (const Document& doc : expected) { - if (!arg.ContainsKey(doc.key())) { + if (!arg.ContainsKey(doc->key())) { return false; } } @@ -319,12 +316,12 @@ TEST(ViewTest, KeepsTrackOfLimboDocuments) { change = view.ApplyChanges(view.ComputeDocumentChanges(DocUpdates({})), MarkCurrent()); ASSERT_THAT(change.limbo_changes(), - ElementsAre(LimboDocumentChange::Added(doc1.key()))); + ElementsAre(LimboDocumentChange::Added(doc1->key()))); change = view.ApplyChanges(view.ComputeDocumentChanges(DocUpdates({})), AckTarget({doc1})); ASSERT_THAT(change.limbo_changes(), - ElementsAre(LimboDocumentChange::Removed(doc1.key()))); + ElementsAre(LimboDocumentChange::Removed(doc1->key()))); change = view.ApplyChanges(view.ComputeDocumentChanges(DocUpdates({doc2})), AckTarget({doc2})); @@ -332,12 +329,12 @@ TEST(ViewTest, KeepsTrackOfLimboDocuments) { change = view.ApplyChanges(view.ComputeDocumentChanges(DocUpdates({doc3}))); ASSERT_THAT(change.limbo_changes(), - ElementsAre(LimboDocumentChange::Added(doc3.key()))); + ElementsAre(LimboDocumentChange::Added(doc3->key()))); change = view.ApplyChanges(view.ComputeDocumentChanges( DocUpdates({DeletedDoc("rooms/eros/messages/2")}))); // remove ASSERT_THAT(change.limbo_changes(), - ElementsAre(LimboDocumentChange::Removed(doc3.key()))); + ElementsAre(LimboDocumentChange::Removed(doc3->key()))); } TEST(ViewTest, ResumingQueryCreatesNoLimbos) { @@ -349,7 +346,7 @@ TEST(ViewTest, ResumingQueryCreatesNoLimbos) { // 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. - View view(query, DocumentKeySet{doc1.key(), doc2.key()}); + View view(query, DocumentKeySet{doc1->key(), doc2->key()}); ViewDocumentChanges changes = view.ComputeDocumentChanges(DocUpdates({})); ViewChange change = view.ApplyChanges(changes, MarkCurrent()); @@ -549,24 +546,22 @@ TEST(ViewTest, ComputesMutatedKeys) { view.ApplyChanges(changes); ASSERT_EQ(changes.mutated_keys(), DocumentKeySet{}); - Document doc3 = - Doc("rooms/eros/messages/2", 0, Map(), DocumentState::kLocalMutations); + Document doc3 = Doc("rooms/eros/messages/2", 0, Map()).SetHasLocalMutations(); changes = view.ComputeDocumentChanges(DocUpdates({doc3})); - ASSERT_EQ(changes.mutated_keys(), DocumentKeySet{doc3.key()}); + ASSERT_EQ(changes.mutated_keys(), DocumentKeySet{doc3->key()}); } TEST(ViewTest, RemovesKeysFromMutatedKeysWhenNewDocHasNoLocalChanges) { Query query = QueryForMessages(); Document doc1 = Doc("rooms/eros/messages/0", 0, Map()); - Document doc2 = - Doc("rooms/eros/messages/1", 0, Map(), DocumentState::kLocalMutations); + Document doc2 = Doc("rooms/eros/messages/1", 0, Map()).SetHasLocalMutations(); View view(query, DocumentKeySet{}); // Start with a full view. ViewDocumentChanges changes = view.ComputeDocumentChanges(DocUpdates({doc1, doc2})); view.ApplyChanges(changes); - ASSERT_EQ(changes.mutated_keys(), (DocumentKeySet{doc2.key()})); + ASSERT_EQ(changes.mutated_keys(), (DocumentKeySet{doc2->key()})); Document doc2_prime = Doc("rooms/eros/messages/1", 0, Map()); changes = view.ComputeDocumentChanges(DocUpdates({doc2_prime})); @@ -577,44 +572,41 @@ TEST(ViewTest, RemovesKeysFromMutatedKeysWhenNewDocHasNoLocalChanges) { TEST(ViewTest, RemembersLocalMutationsFromPreviousSnapshot) { Query query = QueryForMessages(); Document doc1 = Doc("rooms/eros/messages/0", 0, Map()); - Document doc2 = - Doc("rooms/eros/messages/1", 0, Map(), DocumentState::kLocalMutations); + Document doc2 = Doc("rooms/eros/messages/1", 0, Map()).SetHasLocalMutations(); View view(query, DocumentKeySet{}); // Start with a full view. ViewDocumentChanges changes = view.ComputeDocumentChanges(DocUpdates({doc1, doc2})); view.ApplyChanges(changes); - ASSERT_EQ(changes.mutated_keys(), (DocumentKeySet{doc2.key()})); + ASSERT_EQ(changes.mutated_keys(), (DocumentKeySet{doc2->key()})); Document doc3 = Doc("rooms/eros/messages/2", 0, Map()); changes = view.ComputeDocumentChanges(DocUpdates({doc3})); view.ApplyChanges(changes); - ASSERT_EQ(changes.mutated_keys(), (DocumentKeySet{doc2.key()})); + ASSERT_EQ(changes.mutated_keys(), (DocumentKeySet{doc2->key()})); } TEST(ViewTest, RemembersLocalMutationsFromPreviousCallToComputeDocumentChanges) { Query query = QueryForMessages(); Document doc1 = Doc("rooms/eros/messages/0", 0, Map()); - Document doc2 = - Doc("rooms/eros/messages/1", 0, Map(), DocumentState::kLocalMutations); + Document doc2 = Doc("rooms/eros/messages/1", 0, Map()).SetHasLocalMutations(); View view(query, DocumentKeySet{}); // Start with a full view. ViewDocumentChanges changes = view.ComputeDocumentChanges(DocUpdates({doc1, doc2})); - ASSERT_EQ(changes.mutated_keys(), (DocumentKeySet{doc2.key()})); + ASSERT_EQ(changes.mutated_keys(), (DocumentKeySet{doc2->key()})); Document doc3 = Doc("rooms/eros/messages/2", 0, Map()); changes = view.ComputeDocumentChanges(DocUpdates({doc3}), changes); - ASSERT_EQ(changes.mutated_keys(), (DocumentKeySet{doc2.key()})); + ASSERT_EQ(changes.mutated_keys(), (DocumentKeySet{doc2->key()})); } TEST(ViewTest, RaisesHasPendingWritesForPendingMutationsInInitialSnapshot) { Query query = QueryForMessages(); - Document doc1 = - Doc("rooms/eros/messages/1", 0, Map(), DocumentState::kLocalMutations); + Document doc1 = Doc("rooms/eros/messages/1", 0, Map()).SetHasLocalMutations(); View view(query, DocumentKeySet{}); ViewDocumentChanges changes = view.ComputeDocumentChanges(DocUpdates({doc1})); ViewChange view_change = view.ApplyChanges(changes); @@ -624,8 +616,8 @@ TEST(ViewTest, RaisesHasPendingWritesForPendingMutationsInInitialSnapshot) { TEST(ViewTest, DoesntRaiseHasPendingWritesForCommittedMutationsInInitialSnapshot) { Query query = QueryForMessages(); - Document doc1 = Doc("rooms/eros/messages/1", 0, Map(), - DocumentState::kCommittedMutations); + Document doc1 = + Doc("rooms/eros/messages/1", 0, Map()).SetHasCommittedMutations(); View view(query, DocumentKeySet{}); ViewDocumentChanges changes = view.ComputeDocumentChanges(DocUpdates({doc1})); ViewChange view_change = view.ApplyChanges(changes); @@ -638,15 +630,15 @@ TEST(ViewTest, SuppressesWriteAcknowledgementIfWatchHasNotCaughtUp) { // instead wait for Watch to catch up. Query query = QueryForMessages(); - Document doc1 = Doc("rooms/eros/messages/1", 1, Map("time", 1), - DocumentState::kLocalMutations); - Document doc1_committed = Doc("rooms/eros/messages/1", 2, Map("time", 2), - DocumentState::kCommittedMutations); + Document doc1 = + Doc("rooms/eros/messages/1", 1, Map("time", 1)).SetHasLocalMutations(); + Document doc1_committed = Doc("rooms/eros/messages/1", 2, Map("time", 2)) + .SetHasCommittedMutations(); Document doc1_acknowledged = Doc("rooms/eros/messages/1", 2, Map("time", 2)); - Document doc2 = Doc("rooms/eros/messages/2", 1, Map("time", 1), - DocumentState::kLocalMutations); - Document doc2_modified = Doc("rooms/eros/messages/2", 2, Map("time", 3), - DocumentState::kLocalMutations); + Document doc2 = + Doc("rooms/eros/messages/2", 1, Map("time", 1)).SetHasLocalMutations(); + Document doc2_modified = + Doc("rooms/eros/messages/2", 2, Map("time", 3)).SetHasLocalMutations(); Document doc2_acknowledged = Doc("rooms/eros/messages/2", 2, Map("time", 3)); View view(query, DocumentKeySet{}); ViewDocumentChanges changes = diff --git a/Firestore/core/test/unit/local/counting_query_engine.cc b/Firestore/core/test/unit/local/counting_query_engine.cc index a94ea48e0db..c6fb27d7528 100644 --- a/Firestore/core/test/unit/local/counting_query_engine.cc +++ b/Firestore/core/test/unit/local/counting_query_engine.cc @@ -16,8 +16,10 @@ #include "Firestore/core/test/unit/local/counting_query_engine.h" +#include "Firestore/core/src/immutable/sorted_map.h" #include "Firestore/core/src/local/local_documents_view.h" -#include "Firestore/core/src/model/document_map.h" +#include "Firestore/core/src/model/model_fwd.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/mutation_batch.h" #include "Firestore/core/src/nanopb/byte_string.h" @@ -141,7 +143,7 @@ void WrappedMutationQueue::SetLastStreamToken(nanopb::ByteString stream_token) { // MARK: - WrappedRemoteDocumentCache -void WrappedRemoteDocumentCache::Add(const model::MaybeDocument& document, +void WrappedRemoteDocumentCache::Add(const model::MutableDocument& document, const model::SnapshotVersion& read_time) { subject_->Add(document, read_time); } @@ -150,21 +152,21 @@ void WrappedRemoteDocumentCache::Remove(const model::DocumentKey& key) { subject_->Remove(key); } -absl::optional WrappedRemoteDocumentCache::Get( +model::MutableDocument WrappedRemoteDocumentCache::Get( const model::DocumentKey& key) { auto result = subject_->Get(key); - query_engine_->documents_read_by_key_ += result ? 1 : 0; + query_engine_->documents_read_by_key_ += result.is_found_document() ? 1 : 0; return result; } -model::OptionalMaybeDocumentMap WrappedRemoteDocumentCache::GetAll( +model::MutableDocumentMap WrappedRemoteDocumentCache::GetAll( const model::DocumentKeySet& keys) { auto result = subject_->GetAll(keys); query_engine_->documents_read_by_key_ += result.size(); return result; } -DocumentMap WrappedRemoteDocumentCache::GetMatching( +model::MutableDocumentMap WrappedRemoteDocumentCache::GetMatching( const core::Query& query, const model::SnapshotVersion& since_read_time) { auto result = subject_->GetMatching(query, since_read_time); query_engine_->documents_read_by_query_ += result.size(); diff --git a/Firestore/core/test/unit/local/counting_query_engine.h b/Firestore/core/test/unit/local/counting_query_engine.h index e5c99a57149..ae9d1490c02 100644 --- a/Firestore/core/test/unit/local/counting_query_engine.h +++ b/Firestore/core/test/unit/local/counting_query_engine.h @@ -162,18 +162,16 @@ class WrappedRemoteDocumentCache : public RemoteDocumentCache { : subject_(subject), query_engine_(query_engine) { } - void Add(const model::MaybeDocument& document, + void Add(const model::MutableDocument& document, const model::SnapshotVersion& read_time) override; void Remove(const model::DocumentKey& key) override; - absl::optional Get( - const model::DocumentKey& key) override; + model::MutableDocument Get(const model::DocumentKey& key) override; - model::OptionalMaybeDocumentMap GetAll( - const model::DocumentKeySet& keys) override; + model::MutableDocumentMap GetAll(const model::DocumentKeySet& keys) override; - model::DocumentMap GetMatching( + model::MutableDocumentMap GetMatching( const core::Query& query, const model::SnapshotVersion& since_read_time) override; diff --git a/Firestore/core/test/unit/local/leveldb_key_test.cc b/Firestore/core/test/unit/local/leveldb_key_test.cc index a155db83df2..abc3c32a908 100644 --- a/Firestore/core/test/unit/local/leveldb_key_test.cc +++ b/Firestore/core/test/unit/local/leveldb_key_test.cc @@ -16,7 +16,6 @@ #include "Firestore/core/src/local/leveldb_key.h" -#include "Firestore/core/src/model/maybe_document.h" #include "Firestore/core/src/util/string_util.h" #include "Firestore/core/test/unit/testutil/testutil.h" #include "absl/strings/match.h" diff --git a/Firestore/core/test/unit/local/leveldb_migrations_test.cc b/Firestore/core/test/unit/local/leveldb_migrations_test.cc index 9195f7f110c..29acd45ac78 100644 --- a/Firestore/core/test/unit/local/leveldb_migrations_test.cc +++ b/Firestore/core/test/unit/local/leveldb_migrations_test.cc @@ -22,8 +22,11 @@ #include #include "Firestore/Protos/nanopb/firestore/local/mutation.nanopb.h" +#include "Firestore/core/src/core/field_filter.h" +#include "Firestore/core/src/core/query.h" #include "Firestore/core/src/local/leveldb_key.h" #include "Firestore/core/src/local/leveldb_target_cache.h" +#include "Firestore/core/src/local/target_data.h" #include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/util/ordered_code.h" #include "Firestore/core/src/util/path.h" @@ -47,7 +50,9 @@ using model::DocumentKey; using model::ListenSequenceNumber; using model::TargetId; using nanopb::Message; +using testutil::Filter; using testutil::Key; +using testutil::Query; using util::OrderedCode; using util::Path; @@ -72,7 +77,8 @@ class LevelDbMigrationsTest : public testing::Test { protected: void SetUp() override; - std::unique_ptr db_; + std::unique_ptr db_ = nullptr; + std::unique_ptr serializer_ = nullptr; }; void LevelDbMigrationsTest::SetUp() { @@ -86,13 +92,15 @@ void LevelDbMigrationsTest::SetUp() { ASSERT_TRUE(status.ok()) << "Failed to create db: " << status.ToString().c_str(); db_.reset(db); + + serializer_ = absl::make_unique(MakeLocalSerializer()); } TEST_F(LevelDbMigrationsTest, AddsTargetGlobal) { auto metadata = LevelDbTargetCache::TryReadMetadata(db_.get()); ASSERT_TRUE(!metadata) << "Not expecting metadata yet, we should have an empty db"; - LevelDbMigrations::RunMigrations(db_.get()); + LevelDbMigrations::RunMigrations(db_.get(), *serializer_); metadata = LevelDbTargetCache::TryReadMetadata(db_.get()); ASSERT_TRUE(metadata) << "Migrations should have added the metadata"; @@ -103,7 +111,7 @@ TEST_F(LevelDbMigrationsTest, SetsVersionNumber) { ASSERT_EQ(0, initial) << "No version should be equivalent to 0"; // Pick an arbitrary high migration number and migrate to it. - LevelDbMigrations::RunMigrations(db_.get()); + LevelDbMigrations::RunMigrations(db_.get(), *serializer_); SchemaVersion actual = LevelDbMigrations::ReadSchemaVersion(db_.get()); ASSERT_GT(actual, 0) << "Expected to migrate to a schema version > 0"; @@ -145,7 +153,7 @@ TEST_F(LevelDbMigrationsTest, DropsTheTargetCache) { LevelDbMutationKey::Key(user_id, batch_id), }; - LevelDbMigrations::RunMigrations(db_.get(), 2); + LevelDbMigrations::RunMigrations(db_.get(), 2, *serializer_); { // Setup some targets to be counted in the migration. LevelDbTransaction transaction(db_.get(), @@ -159,7 +167,7 @@ TEST_F(LevelDbMigrationsTest, DropsTheTargetCache) { transaction.Commit(); } - LevelDbMigrations::RunMigrations(db_.get(), 3); + LevelDbMigrations::RunMigrations(db_.get(), 3, *serializer_); { LevelDbTransaction transaction(db_.get(), "test_drops_the_target_cache"); for (const std::string& key : target_keys) { @@ -176,7 +184,7 @@ TEST_F(LevelDbMigrationsTest, DropsTheTargetCache) { } TEST_F(LevelDbMigrationsTest, DropsTheTargetCacheWithThousandsOfEntries) { - LevelDbMigrations::RunMigrations(db_.get(), 2); + LevelDbMigrations::RunMigrations(db_.get(), 2, *serializer_); { // Setup some targets to be destroyed. LevelDbTransaction transaction( @@ -188,7 +196,7 @@ TEST_F(LevelDbMigrationsTest, DropsTheTargetCacheWithThousandsOfEntries) { transaction.Commit(); } - LevelDbMigrations::RunMigrations(db_.get(), 3); + LevelDbMigrations::RunMigrations(db_.get(), 3, *serializer_); { LevelDbTransaction transaction(db_.get(), "Verify"); std::string prefix = LevelDbTargetKey::KeyPrefix(); @@ -209,7 +217,7 @@ TEST_F(LevelDbMigrationsTest, AddsSentinelRows) { ListenSequenceNumber new_sequence_number = 2; std::string encoded_old_sequence_number = LevelDbDocumentTargetKey::EncodeSentinelValue(old_sequence_number); - LevelDbMigrations::RunMigrations(db_.get(), 3); + LevelDbMigrations::RunMigrations(db_.get(), 3, *serializer_); { std::string empty_buffer; LevelDbTransaction transaction(db_.get(), "Setup"); @@ -234,7 +242,7 @@ TEST_F(LevelDbMigrationsTest, AddsSentinelRows) { transaction.Commit(); } - LevelDbMigrations::RunMigrations(db_.get(), 4); + LevelDbMigrations::RunMigrations(db_.get(), 4, *serializer_); { LevelDbTransaction transaction(db_.get(), "Verify"); auto it = transaction.NewIterator(); @@ -271,7 +279,7 @@ TEST_F(LevelDbMigrationsTest, RemovesMutationBatches) { DocumentKey test_write_baz = DocumentKey::FromPathString("docs/baz"); DocumentKey test_write_pending = DocumentKey::FromPathString("docs/pending"); // Do everything up until the mutation batch migration. - LevelDbMigrations::RunMigrations(db_.get(), 3); + LevelDbMigrations::RunMigrations(db_.get(), 3, *serializer_); // Set up data { LevelDbTransaction transaction(db_.get(), "Setup Foo"); @@ -344,7 +352,7 @@ TEST_F(LevelDbMigrationsTest, RemovesMutationBatches) { transaction.Commit(); } - LevelDbMigrations::RunMigrations(db_.get(), 5); + LevelDbMigrations::RunMigrations(db_.get(), 5, *serializer_); { // Verify @@ -412,7 +420,7 @@ TEST_F(LevelDbMigrationsTest, CreateCollectionParentsIndex) { {"cg3", {"blah/x/blah/x", "cg2/x"}}}; std::string empty_buffer; - LevelDbMigrations::RunMigrations(db_.get(), 5); + LevelDbMigrations::RunMigrations(db_.get(), 5, *serializer_); { LevelDbTransaction transaction(db_.get(), "Write Mutations and Remote Documents"); @@ -436,7 +444,7 @@ TEST_F(LevelDbMigrationsTest, CreateCollectionParentsIndex) { } // Migrate to v6 and verify index entries. - LevelDbMigrations::RunMigrations(db_.get(), 6); + LevelDbMigrations::RunMigrations(db_.get(), 6, *serializer_); { LevelDbTransaction transaction(db_.get(), "Verify"); @@ -459,22 +467,68 @@ TEST_F(LevelDbMigrationsTest, CreateCollectionParentsIndex) { } } +TEST_F(LevelDbMigrationsTest, RewritesCanonicalIds) { + LevelDbMigrations::RunMigrations(db_.get(), 6, *serializer_); + auto query = Query("collection").AddingFilter(Filter("foo", "==", "bar")); + TargetData initial_target_data(query.ToTarget(), + /* target_id= */ 2, + /* sequence_number= */ 1, + QueryPurpose::Listen); + auto invalid_key = LevelDbQueryTargetKey::Key( + "invalid_canonical_id", initial_target_data.target_id()); + + // Write the target with invalid canonical id into leveldb. + { + LevelDbTransaction transaction(db_.get(), + "Write target with invalid canonical ID"); + auto target_key = LevelDbTargetKey::Key(2); + transaction.Put(target_key, + serializer_->EncodeTargetData(initial_target_data)); + + std::string empty_buffer; + transaction.Put(invalid_key, empty_buffer); + + transaction.Commit(); + } + + // Run migration and verify canonical id is rewritten with valid string. + { + LevelDbMigrations::RunMigrations(db_.get(), *serializer_); + + LevelDbTransaction transaction( + db_.get(), "Read target to verify canonical ID rewritten"); + + auto query_target_key = + LevelDbQueryTargetKey::Key(initial_target_data.target().CanonicalId(), + initial_target_data.target_id()); + auto it = transaction.NewIterator(); + // Verify we are able to seek to the key built with proper canonical ID. + it->Seek(query_target_key); + ASSERT_EQ(it->key(), query_target_key); + + // Verify original invalid key is deleted. + it->Seek(invalid_key); + ASSERT_NE(it->key(), invalid_key); + transaction.Commit(); + } +} + TEST_F(LevelDbMigrationsTest, CanDowngrade) { // First, run all of the migrations - LevelDbMigrations::RunMigrations(db_.get()); + LevelDbMigrations::RunMigrations(db_.get(), *serializer_); LevelDbMigrations::SchemaVersion latest_version = LevelDbMigrations::ReadSchemaVersion(db_.get()); // Downgrade to an early version. LevelDbMigrations::SchemaVersion downgrade_version = 1; - LevelDbMigrations::RunMigrations(db_.get(), downgrade_version); + LevelDbMigrations::RunMigrations(db_.get(), downgrade_version, *serializer_); LevelDbMigrations::SchemaVersion post_downgrade_version = LevelDbMigrations::ReadSchemaVersion(db_.get()); ASSERT_EQ(downgrade_version, post_downgrade_version); // Verify that we can upgrade again to the latest version. - LevelDbMigrations::RunMigrations(db_.get()); + LevelDbMigrations::RunMigrations(db_.get(), *serializer_); LevelDbMigrations::SchemaVersion final_version = LevelDbMigrations::ReadSchemaVersion(db_.get()); ASSERT_EQ(final_version, latest_version); diff --git a/Firestore/core/test/unit/local/local_serializer_test.cc b/Firestore/core/test/unit/local/local_serializer_test.cc index de533a5c872..9fcf4240946 100644 --- a/Firestore/core/test/unit/local/local_serializer_test.cc +++ b/Firestore/core/test/unit/local/local_serializer_test.cc @@ -20,7 +20,6 @@ #include "Firestore/Protos/cpp/firestore/local/maybe_document.pb.h" #include "Firestore/Protos/cpp/firestore/local/mutation.pb.h" #include "Firestore/Protos/cpp/firestore/local/target.pb.h" -#include "Firestore/Protos/cpp/google/firestore/v1/firestore.pb.h" #include "Firestore/core/src/bundle/bundled_query.h" #include "Firestore/core/src/bundle/named_query.h" #include "Firestore/core/src/core/field_filter.h" @@ -28,19 +27,16 @@ #include "Firestore/core/src/core/target.h" #include "Firestore/core/src/local/target_data.h" #include "Firestore/core/src/model/delete_mutation.h" -#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/field_mask.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/model/maybe_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/mutation.h" #include "Firestore/core/src/model/mutation_batch.h" -#include "Firestore/core/src/model/no_document.h" #include "Firestore/core/src/model/patch_mutation.h" #include "Firestore/core/src/model/precondition.h" #include "Firestore/core/src/model/set_mutation.h" #include "Firestore/core/src/model/snapshot_version.h" #include "Firestore/core/src/model/types.h" -#include "Firestore/core/src/model/unknown_document.h" +#include "Firestore/core/src/model/value_util.h" #include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/nanopb/nanopb_util.h" #include "Firestore/core/src/nanopb/reader.h" @@ -65,30 +61,30 @@ using core::Query; using core::Target; using ::google::protobuf::util::MessageDifferencer; using model::DatabaseId; -using model::Document; using model::DocumentKey; -using model::DocumentState; using model::FieldMask; using model::FieldPath; -using model::FieldValue; using model::ListenSequenceNumber; -using model::MaybeDocument; +using model::MutableDocument; using model::Mutation; using model::MutationBatch; -using model::NoDocument; using model::ObjectValue; using model::PatchMutation; using model::Precondition; using model::SetMutation; using model::SnapshotVersion; using model::TargetId; -using model::UnknownDocument; using nanopb::ByteString; using nanopb::ByteStringWriter; using nanopb::FreeNanopbMessage; +using nanopb::MakeArray; +using nanopb::MakeBytesArray; +using nanopb::MakeMessage; +using nanopb::MakeStdString; using nanopb::Message; using nanopb::ProtobufParse; using nanopb::ProtobufSerialize; +using nanopb::SetRepeatedField; using nanopb::StringReader; using nanopb::Writer; using testutil::DeletedDoc; @@ -100,6 +96,7 @@ using testutil::Map; using testutil::OrderBy; using testutil::Query; using testutil::UnknownDoc; +using testutil::Value; using testutil::WrapObject; using util::Status; @@ -197,7 +194,7 @@ class LocalSerializerTest : public ::testing::Test { return write_time_proto; } - static void ExpectSet(_google_firestore_v1_Write encoded) { + static void ExpectSet(google_firestore_v1_Write encoded) { EXPECT_EQ(google_firestore_v1_Write_update_tag, encoded.which_operation); EXPECT_EQ(2, encoded.update.fields_count); EXPECT_EQ("a", nanopb::MakeString(encoded.update.fields[0].key)); @@ -209,7 +206,7 @@ class LocalSerializerTest : public ::testing::Test { EXPECT_FALSE(encoded.has_current_document); } - static void ExpectPatch(_google_firestore_v1_Write encoded) { + static void ExpectPatch(google_firestore_v1_Write encoded) { EXPECT_EQ(google_firestore_v1_Write_update_tag, encoded.which_operation); EXPECT_EQ(2, encoded.update.fields_count); EXPECT_EQ("a", nanopb::MakeString(encoded.update.fields[0].key)); @@ -223,11 +220,11 @@ class LocalSerializerTest : public ::testing::Test { EXPECT_TRUE(encoded.current_document.exists); } - static void ExpectDelete(_google_firestore_v1_Write encoded) { + static void ExpectDelete(google_firestore_v1_Write encoded) { EXPECT_EQ(google_firestore_v1_Write_delete_tag, encoded.which_operation); } - static void ExpectUpdateTransform(_google_firestore_v1_Write encoded) { + static void ExpectUpdateTransform(google_firestore_v1_Write encoded) { EXPECT_EQ(2, encoded.update_transforms_count); EXPECT_EQ( google_firestore_v1_DocumentTransform_FieldTransform_increment_tag, @@ -243,37 +240,33 @@ class LocalSerializerTest : public ::testing::Test { EXPECT_EQ(13.37, encoded.update_transforms[1].increment.double_value); } - static void ExpectNoUpdateTransform(_google_firestore_v1_Write encoded) { + static void ExpectNoUpdateTransform(google_firestore_v1_Write encoded) { EXPECT_EQ(0, encoded.update_transforms_count); } private: void ExpectSerializationRoundTrip( - const MaybeDocument& model, - const ::firestore::client::MaybeDocument& proto, - MaybeDocument::Type type) { - EXPECT_EQ(type, model.type()); + const MutableDocument& model, + const ::firestore::client::MaybeDocument& proto) { ByteString bytes = EncodeMaybeDocument(&serializer, model); auto actual = ProtobufParse<::firestore::client::MaybeDocument>(bytes); EXPECT_TRUE(msg_diff.Compare(proto, actual)) << message_differences; } void ExpectDeserializationRoundTrip( - const MaybeDocument& model, - const ::firestore::client::MaybeDocument& proto, - MaybeDocument::Type type) { + const MutableDocument& model, + const ::firestore::client::MaybeDocument& proto) { ByteString bytes = ProtobufSerialize(proto); StringReader reader(bytes); auto message = Message::TryParse(&reader); auto actual_model = serializer.DecodeMaybeDocument(&reader, *message); EXPECT_OK(reader.status()); - EXPECT_EQ(type, actual_model.type()); EXPECT_EQ(model, actual_model); } ByteString EncodeMaybeDocument(local::LocalSerializer* localSerializer, - const MaybeDocument& maybe_doc) { - return MakeByteString(localSerializer->EncodeMaybeDocument(maybe_doc)); + const MutableDocument& document) { + return MakeByteString(localSerializer->EncodeMaybeDocument(document)); } void ExpectSerializationRoundTrip(const TargetData& target_data, @@ -373,10 +366,10 @@ TEST_F(LocalSerializerTest, SetMutationAndTransformMutationAreSquashed) { ASSERT_EQ(1, decoded.mutations().size()); ASSERT_EQ(Mutation::Type::Set, decoded.mutations()[0].type()); - google_firestore_v1_Write encoded = - remote_serializer.EncodeMutation(decoded.mutations()[0]); - ExpectSet(encoded); - ExpectUpdateTransform(encoded); + Message encoded{ + remote_serializer.EncodeMutation(decoded.mutations()[0])}; + ExpectSet(*encoded); + ExpectUpdateTransform(*encoded); } // TODO(b/174608374): Remove these tests once we perform a schema migration. @@ -394,10 +387,10 @@ TEST_F(LocalSerializerTest, PatchMutationAndTransformMutationAreSquashed) { ASSERT_EQ(1, decoded.mutations().size()); ASSERT_EQ(Mutation::Type::Patch, decoded.mutations()[0].type()); - google_firestore_v1_Write encoded = - remote_serializer.EncodeMutation(decoded.mutations()[0]); - ExpectPatch(encoded); - ExpectUpdateTransform(encoded); + Message encoded{ + remote_serializer.EncodeMutation(decoded.mutations()[0])}; + ExpectPatch(*encoded); + ExpectUpdateTransform(*encoded); } // TODO(b/174608374): Remove these tests once we perform a schema migration. @@ -446,21 +439,30 @@ TEST_F(LocalSerializerTest, MultipleMutationsAreSquashed) { auto message = Message::TryParse(&reader); MutationBatch decoded = serializer.DecodeMutationBatch(&reader, *message); ASSERT_EQ(5, decoded.mutations().size()); - _google_firestore_v1_Write encoded = - remote_serializer.EncodeMutation(decoded.mutations()[0]); - ExpectSet(encoded); - ExpectNoUpdateTransform(encoded); - encoded = remote_serializer.EncodeMutation(decoded.mutations()[1]); - ExpectSet(encoded); - ExpectUpdateTransform(encoded); - encoded = remote_serializer.EncodeMutation(decoded.mutations()[2]); - ExpectDelete(encoded); - encoded = remote_serializer.EncodeMutation(decoded.mutations()[3]); - ExpectPatch(encoded); - ExpectUpdateTransform(encoded); - encoded = remote_serializer.EncodeMutation(decoded.mutations()[4]); - ExpectPatch(encoded); - ExpectNoUpdateTransform(encoded); + + Message encoded{ + remote_serializer.EncodeMutation(decoded.mutations()[0])}; + ExpectSet(*encoded); + ExpectNoUpdateTransform(*encoded); + + encoded = + MakeMessage(remote_serializer.EncodeMutation(decoded.mutations()[1])); + ExpectSet(*encoded); + ExpectUpdateTransform(*encoded); + + encoded = + MakeMessage(remote_serializer.EncodeMutation(decoded.mutations()[2])); + ExpectDelete(*encoded); + + encoded = + MakeMessage(remote_serializer.EncodeMutation(decoded.mutations()[3])); + ExpectPatch(*encoded); + ExpectUpdateTransform(*encoded); + + encoded = + MakeMessage(remote_serializer.EncodeMutation(decoded.mutations()[4])); + ExpectPatch(*encoded); + ExpectNoUpdateTransform(*encoded); } TEST_F(LocalSerializerTest, EncodesMutationBatch) { @@ -498,7 +500,7 @@ TEST_F(LocalSerializerTest, EncodesMutationBatch) { } TEST_F(LocalSerializerTest, EncodesDocumentAsMaybeDocument) { - Document doc = Doc("some/path", /*version=*/42, Map("foo", "bar")); + MutableDocument doc = Doc("some/path", /*version=*/42, Map("foo", "bar")); ::firestore::client::MaybeDocument maybe_doc_proto; maybe_doc_proto.mutable_document()->set_name( @@ -510,18 +512,18 @@ TEST_F(LocalSerializerTest, EncodesDocumentAsMaybeDocument) { maybe_doc_proto.mutable_document()->mutable_update_time()->set_seconds(0); maybe_doc_proto.mutable_document()->mutable_update_time()->set_nanos(42000); - ExpectRoundTrip(doc, maybe_doc_proto, doc.type()); + ExpectRoundTrip(doc, maybe_doc_proto); // Verify has_committed_mutations - doc = Doc("some/path", /*version=*/42, Map("foo", "bar"), - DocumentState::kCommittedMutations); + doc = Doc("some/path", /*version=*/42, Map("foo", "bar")) + .SetHasCommittedMutations(); maybe_doc_proto.set_has_committed_mutations(true); - ExpectRoundTrip(doc, maybe_doc_proto, doc.type()); + ExpectRoundTrip(doc, maybe_doc_proto); } TEST_F(LocalSerializerTest, EncodesNoDocumentAsMaybeDocument) { - NoDocument no_doc = DeletedDoc("some/path", /*version=*/42); + MutableDocument no_doc = DeletedDoc("some/path", /*version=*/42); ::firestore::client::MaybeDocument maybe_doc_proto; maybe_doc_proto.mutable_no_document()->set_name( @@ -529,18 +531,17 @@ TEST_F(LocalSerializerTest, EncodesNoDocumentAsMaybeDocument) { maybe_doc_proto.mutable_no_document()->mutable_read_time()->set_seconds(0); maybe_doc_proto.mutable_no_document()->mutable_read_time()->set_nanos(42000); - ExpectRoundTrip(no_doc, maybe_doc_proto, no_doc.type()); + ExpectRoundTrip(no_doc, maybe_doc_proto); // Verify has_committed_mutations - no_doc = - DeletedDoc("some/path", /*version=*/42, /*has_committed_mutations=*/true); + no_doc = DeletedDoc("some/path", /*version=*/42).SetHasCommittedMutations(); maybe_doc_proto.set_has_committed_mutations(true); - ExpectRoundTrip(no_doc, maybe_doc_proto, no_doc.type()); + ExpectRoundTrip(no_doc, maybe_doc_proto); } TEST_F(LocalSerializerTest, EncodesUnknownDocumentAsMaybeDocument) { - UnknownDocument unknown_doc = UnknownDoc("some/path", /*version=*/42); + MutableDocument unknown_doc = UnknownDoc("some/path", /*version=*/42); ::firestore::client::MaybeDocument maybe_doc_proto; maybe_doc_proto.mutable_unknown_document()->set_name( @@ -550,7 +551,7 @@ TEST_F(LocalSerializerTest, EncodesUnknownDocumentAsMaybeDocument) { 42000); maybe_doc_proto.set_has_committed_mutations(true); - ExpectRoundTrip(unknown_doc, maybe_doc_proto, unknown_doc.type()); + ExpectRoundTrip(unknown_doc, maybe_doc_proto); } TEST_F(LocalSerializerTest, EncodesTargetData) { diff --git a/Firestore/core/test/unit/local/local_store_test.cc b/Firestore/core/test/unit/local/local_store_test.cc index 122f678e0de..16b66bf95c7 100644 --- a/Firestore/core/test/unit/local/local_store_test.cc +++ b/Firestore/core/test/unit/local/local_store_test.cc @@ -30,13 +30,12 @@ #include "Firestore/core/src/local/query_result.h" #include "Firestore/core/src/local/target_data.h" #include "Firestore/core/src/model/delete_mutation.h" -#include "Firestore/core/src/model/document_map.h" +#include "Firestore/core/src/model/document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/mutation_batch_result.h" -#include "Firestore/core/src/model/no_document.h" #include "Firestore/core/src/model/patch_mutation.h" #include "Firestore/core/src/model/set_mutation.h" #include "Firestore/core/src/model/transform_operation.h" -#include "Firestore/core/src/model/unknown_document.h" #include "Firestore/core/src/remote/remote_event.h" #include "Firestore/core/src/remote/watch_change.h" #include "Firestore/core/test/unit/remote/fake_target_metadata_provider.h" @@ -57,11 +56,9 @@ using model::Document; using model::DocumentKey; using model::DocumentKeySet; using model::DocumentMap; -using model::DocumentState; -using model::FieldValue; using model::ListenSequenceNumber; -using model::MaybeDocument; -using model::MaybeDocumentMap; +using model::MutableDocument; +using model::MutableDocumentMap; using model::Mutation; using model::MutationBatch; using model::MutationBatchResult; @@ -71,6 +68,7 @@ using model::ResourcePath; using model::SnapshotVersion; using model::TargetId; using nanopb::ByteString; +using nanopb::Message; using remote::DocumentWatchChange; using remote::FakeTargetMetadataProvider; using remote::RemoteEvent; @@ -89,24 +87,16 @@ using testutil::UnknownDoc; using testutil::Value; using testutil::Vector; -std::vector DocMapToVector(const MaybeDocumentMap& docs) { - std::vector result; - for (const auto& kv : docs) { - result.push_back(kv.second); - } - return result; -} - std::vector DocMapToVector(const DocumentMap& docs) { std::vector result; - for (const auto& kv : docs.underlying_map()) { - result.push_back(Document(kv.second)); + for (const auto& kv : docs) { + result.push_back(kv.second); } return result; } -MaybeDocumentMap DocVectorToMap(const std::vector& docs) { - MaybeDocumentMap result; +MutableDocumentMap DocVectorToMap(const std::vector& docs) { + MutableDocumentMap result; for (const auto& d : docs) { result = result.insert(d.key(), d); } @@ -114,11 +104,11 @@ MaybeDocumentMap DocVectorToMap(const std::vector& docs) { } RemoteEvent UpdateRemoteEventWithLimboTargets( - const MaybeDocument& doc, + const MutableDocument& doc, const std::vector& updated_in_targets, const std::vector& removed_from_targets, const std::vector& limbo_targets) { - HARD_ASSERT(!doc.is_document() || !Document(doc).has_local_mutations(), + HARD_ASSERT(!doc.is_found_document() || !doc.has_local_mutations(), "Docs from remote updates shouldn't have local changes."); DocumentWatchChange change{updated_in_targets, removed_from_targets, doc.key(), doc}; @@ -158,7 +148,7 @@ RemoteEvent NoChangeEvent(int target_id, int version) { } /** Creates a remote event that inserts a list of documents. */ -RemoteEvent AddedRemoteEvent(const std::vector& docs, +RemoteEvent AddedRemoteEvent(const std::vector& docs, const std::vector& added_to_targets) { HARD_ASSERT(!docs.empty(), "Cannot pass empty docs array"); @@ -169,8 +159,8 @@ RemoteEvent AddedRemoteEvent(const std::vector& docs, WatchChangeAggregator aggregator{&metadata_provider}; SnapshotVersion version; - for (const MaybeDocument& doc : docs) { - HARD_ASSERT(!doc.is_document() || !Document(doc).has_local_mutations(), + for (const MutableDocument& doc : docs) { + HARD_ASSERT(!doc.has_local_mutations(), "Docs from remote updates shouldn't have local changes."); DocumentWatchChange change{added_to_targets, {}, doc.key(), doc}; aggregator.HandleDocumentChange(change); @@ -181,15 +171,15 @@ RemoteEvent AddedRemoteEvent(const std::vector& docs, } /** Creates a remote event that inserts a new document. */ -RemoteEvent AddedRemoteEvent(const MaybeDocument& doc, +RemoteEvent AddedRemoteEvent(const MutableDocument& doc, const std::vector& added_to_targets) { - std::vector docs{doc}; + std::vector docs{doc}; return AddedRemoteEvent(docs, added_to_targets); } /** Creates a remote event with changes to a document. */ RemoteEvent UpdateRemoteEvent( - const MaybeDocument& doc, + const MutableDocument& doc, const std::vector& updated_in_targets, const std::vector& removed_from_targets) { return UpdateRemoteEventWithLimboTargets(doc, updated_in_targets, @@ -249,7 +239,8 @@ void LocalStoreTest::UpdateViews(int target_id, bool from_cache) { } void LocalStoreTest::AcknowledgeMutationWithVersion( - int64_t document_version, absl::optional transform_result) { + int64_t document_version, + absl::optional> transform_result) { ASSERT_GT(batches_.size(), 0) << "Missing batch to acknowledge."; MutationBatch batch = batches_.front(); batches_.erase(batches_.begin()); @@ -258,13 +249,15 @@ void LocalStoreTest::AcknowledgeMutationWithVersion( << "Acknowledging more than one mutation not supported."; SnapshotVersion version = testutil::Version(document_version); - absl::optional> mutation_transform_result; + Message mutation_transform_result{}; if (transform_result) { - mutation_transform_result = std::vector{*transform_result}; + mutation_transform_result = Array(std::move(*transform_result)); } - MutationResult mutation_result(version, mutation_transform_result); - MutationBatchResult result(batch, version, {mutation_result}, {}); + MutationResult mutation_result(version, std::move(mutation_transform_result)); + std::vector mutation_results; + mutation_results.emplace_back(std::move(mutation_result)); + MutationBatchResult result(batch, version, std::move(mutation_results), {}); last_changes_ = local_store_.AcknowledgeBatch(result); } @@ -294,7 +287,7 @@ QueryResult LocalStoreTest::ExecuteQuery(const core::Query& query) { } void LocalStoreTest::ApplyBundledDocuments( - const std::vector& documents) { + const std::vector& documents) { last_changes_ = local_store_.ApplyBundledDocuments(DocVectorToMap(documents), ""); } @@ -312,29 +305,29 @@ void LocalStoreTest::ResetPersistenceStats() { /** Asserts that a the last_changes contain the docs in the given array. */ #define FSTAssertChanged(...) \ do { \ - std::vector expected = {__VA_ARGS__}; \ + std::vector expected = {__VA_ARGS__}; \ ASSERT_EQ(last_changes_.size(), expected.size()); \ auto last_changes_list = DocMapToVector(last_changes_); \ ASSERT_EQ(last_changes_list, expected); \ - last_changes_ = MaybeDocumentMap{}; \ + last_changes_ = DocumentMap{}; \ } while (0) /** * Asserts that the last ExecuteQuery results contain the docs in the given * array. */ -#define FSTAssertQueryReturned(...) \ - do { \ - std::vector expected_keys = {__VA_ARGS__}; \ - ASSERT_EQ(last_query_result_.documents().size(), expected_keys.size()); \ - auto expected_keys_iterator = expected_keys.begin(); \ - for (const auto& kv : last_query_result_.documents().underlying_map()) { \ - const DocumentKey& actual_key = kv.first; \ - DocumentKey expected_key = Key(*expected_keys_iterator); \ - ASSERT_EQ(actual_key, expected_key); \ - ++expected_keys_iterator; \ - } \ - last_query_result_ = QueryResult{}; \ +#define FSTAssertQueryReturned(...) \ + do { \ + std::vector expected_keys = {__VA_ARGS__}; \ + ASSERT_EQ(last_query_result_.documents().size(), expected_keys.size()); \ + auto expected_keys_iterator = expected_keys.begin(); \ + for (const auto& kv : last_query_result_.documents()) { \ + const DocumentKey& actual_key = kv.first; \ + DocumentKey expected_key = Key(*expected_keys_iterator); \ + ASSERT_EQ(actual_key, expected_key); \ + ++expected_keys_iterator; \ + } \ + last_query_result_ = QueryResult{}; \ } while (0) /** Asserts that the given keys were removed. */ @@ -345,30 +338,29 @@ void LocalStoreTest::ResetPersistenceStats() { auto key_path_iterator = key_paths.begin(); \ for (const auto& kv : last_changes_) { \ const DocumentKey& actual_key = kv.first; \ - const MaybeDocument& value = kv.second; \ + const Document& value = kv.second; \ DocumentKey expected_key = Key(*key_path_iterator); \ ASSERT_EQ(actual_key, expected_key); \ - ASSERT_TRUE(value.is_no_document()); \ + ASSERT_FALSE(value->is_found_document()); \ ++key_path_iterator; \ } \ - last_changes_ = MaybeDocumentMap{}; \ + last_changes_ = DocumentMap{}; \ } while (0) /** Asserts that the given local store contains the given document. */ -#define FSTAssertContains(document) \ - do { \ - MaybeDocument expected = (document); \ - absl::optional actual = \ - local_store_.ReadDocument(expected.key()); \ - ASSERT_EQ(actual, expected); \ +#define FSTAssertContains(document) \ + do { \ + MutableDocument expected = (document); \ + Document actual = local_store_.ReadDocument(expected.key()); \ + ASSERT_EQ(actual, expected); \ } while (0) /** Asserts that the given local store does not contain the given document. */ -#define FSTAssertNotContains(key_path_string) \ - do { \ - DocumentKey key = Key(key_path_string); \ - absl::optional actual = local_store_.ReadDocument(key); \ - ASSERT_EQ(actual, absl::nullopt); \ +#define FSTAssertNotContains(key_path_string) \ + do { \ + DocumentKey key = Key(key_path_string); \ + Document actual = local_store_.ReadDocument(key); \ + ASSERT_FALSE(actual->is_valid_document()); \ } while (0) /** @@ -416,39 +408,36 @@ TEST_P(LocalStoreTest, MutationBatchKeys) { TEST_P(LocalStoreTest, HandlesSetMutation) { WriteMutation(testutil::SetMutation("foo/bar", Map("foo", "bar"))); - FSTAssertChanged( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); - AcknowledgeMutationWithVersion(0); + AcknowledgeMutationWithVersion(1); FSTAssertChanged( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kCommittedMutations)); + Doc("foo/bar", 1, Map("foo", "bar")).SetHasCommittedMutations()); if (IsGcEager()) { // Nothing is pinning this anymore, as it has been acknowledged and there // are no targets active. FSTAssertNotContains("foo/bar"); } else { - FSTAssertContains(Doc("foo/bar", 0, Map("foo", "bar"), - DocumentState::kCommittedMutations)); + FSTAssertContains( + Doc("foo/bar", 1, Map("foo", "bar")).SetHasCommittedMutations()); } } TEST_P(LocalStoreTest, HandlesSetMutationThenDocument) { WriteMutation(testutil::SetMutation("foo/bar", Map("foo", "bar"))); - FSTAssertChanged( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); TargetId target_id = AllocateQuery(Query("foo")); ApplyRemoteEvent(UpdateRemoteEvent(Doc("foo/bar", 2, Map("it", "changed")), {target_id}, {})); - FSTAssertChanged( - Doc("foo/bar", 2, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 2, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 2, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 2, Map("foo", "bar")).SetHasLocalMutations()); } TEST_P(LocalStoreTest, HandlesAckThenRejectThenRemoteEvent) { @@ -457,30 +446,28 @@ TEST_P(LocalStoreTest, HandlesAckThenRejectThenRemoteEvent) { TargetId target_id = AllocateQuery(query); WriteMutation(testutil::SetMutation("foo/bar", Map("foo", "bar"))); - FSTAssertChanged( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); // The last seen version is zero, so this ack must be held. AcknowledgeMutationWithVersion(1); FSTAssertChanged( - Doc("foo/bar", 1, Map("foo", "bar"), DocumentState::kCommittedMutations)); + Doc("foo/bar", 1, Map("foo", "bar")).SetHasCommittedMutations()); // Under eager GC, there is no longer a reference for the document, and it // should be deleted. if (IsGcEager()) { FSTAssertNotContains("foo/bar"); } else { - FSTAssertContains(Doc("foo/bar", 1, Map("foo", "bar"), - DocumentState::kCommittedMutations)); + FSTAssertContains( + Doc("foo/bar", 1, Map("foo", "bar")).SetHasCommittedMutations()); } WriteMutation(testutil::SetMutation("bar/baz", Map("bar", "baz"))); - FSTAssertChanged( - Doc("bar/baz", 0, Map("bar", "baz"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("bar/baz", 0, Map("bar", "baz")).SetHasLocalMutations()); FSTAssertContains( - Doc("bar/baz", 0, Map("bar", "baz"), DocumentState::kLocalMutations)); + Doc("bar/baz", 0, Map("bar", "baz")).SetHasLocalMutations()); RejectMutation(); FSTAssertRemoved("bar/baz"); @@ -503,25 +490,24 @@ TEST_P(LocalStoreTest, HandlesDeletedDocumentThenSetMutationThenAck) { // Under eager GC, there is no longer a reference for the document, and it // should be deleted. if (!IsGcEager()) { - FSTAssertContains(DeletedDoc("foo/bar", 2, false)); + FSTAssertContains(DeletedDoc("foo/bar", 2)); } else { FSTAssertNotContains("foo/bar"); } WriteMutation(testutil::SetMutation("foo/bar", Map("foo", "bar"))); - FSTAssertChanged( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); // Can now remove the target, since we have a mutation pinning the document local_store_.ReleaseTarget(target_id); // Verify we didn't lose anything FSTAssertContains( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); AcknowledgeMutationWithVersion(3); FSTAssertChanged( - Doc("foo/bar", 3, Map("foo", "bar"), DocumentState::kCommittedMutations)); + Doc("foo/bar", 3, Map("foo", "bar")).SetHasCommittedMutations()); // It has been acknowledged, and should no longer be retained as there is no // target and mutation if (IsGcEager()) { @@ -534,15 +520,13 @@ TEST_P(LocalStoreTest, HandlesSetMutationThenDeletedDocument) { TargetId target_id = AllocateQuery(query); WriteMutation(testutil::SetMutation("foo/bar", Map("foo", "bar"))); - FSTAssertChanged( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); ApplyRemoteEvent( UpdateRemoteEvent(DeletedDoc("foo/bar", 2), {target_id}, {})); - FSTAssertChanged( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); } TEST_P(LocalStoreTest, HandlesDocumentThenSetMutationThenAckThenDocument) { @@ -556,17 +540,16 @@ TEST_P(LocalStoreTest, HandlesDocumentThenSetMutationThenAckThenDocument) { FSTAssertContains(Doc("foo/bar", 2, Map("it", "base"))); WriteMutation(testutil::SetMutation("foo/bar", Map("foo", "bar"))); - FSTAssertChanged( - Doc("foo/bar", 2, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 2, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 2, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 2, Map("foo", "bar")).SetHasLocalMutations()); AcknowledgeMutationWithVersion(3); // we haven't seen the remote event yet, so the write is still held. FSTAssertChanged( - Doc("foo/bar", 3, Map("foo", "bar"), DocumentState::kCommittedMutations)); + Doc("foo/bar", 3, Map("foo", "bar")).SetHasCommittedMutations()); FSTAssertContains( - Doc("foo/bar", 3, Map("foo", "bar"), DocumentState::kCommittedMutations)); + Doc("foo/bar", 3, Map("foo", "bar")).SetHasCommittedMutations()); ApplyRemoteEvent(UpdateRemoteEvent(Doc("foo/bar", 3, Map("it", "changed")), {target_id}, {})); @@ -598,18 +581,18 @@ TEST_P(LocalStoreTest, HandlesPatchMutationThenDocumentThenAck) { ApplyRemoteEvent( AddedRemoteEvent(Doc("foo/bar", 1, Map("it", "base")), {target_id})); - FSTAssertChanged(Doc("foo/bar", 1, Map("foo", "bar", "it", "base"), - DocumentState::kLocalMutations)); - FSTAssertContains(Doc("foo/bar", 1, Map("foo", "bar", "it", "base"), - DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 1, Map("foo", "bar", "it", "base")) + .SetHasLocalMutations()); + FSTAssertContains(Doc("foo/bar", 1, Map("foo", "bar", "it", "base")) + .SetHasLocalMutations()); AcknowledgeMutationWithVersion(2); // We still haven't seen the remote events for the patch, so the local changes // remain, and there are no changes - FSTAssertChanged(Doc("foo/bar", 2, Map("foo", "bar", "it", "base"), - DocumentState::kCommittedMutations)); - FSTAssertContains(Doc("foo/bar", 2, Map("foo", "bar", "it", "base"), - DocumentState::kCommittedMutations)); + FSTAssertChanged(Doc("foo/bar", 2, Map("foo", "bar", "it", "base")) + .SetHasCommittedMutations()); + FSTAssertContains(Doc("foo/bar", 2, Map("foo", "bar", "it", "base")) + .SetHasCommittedMutations()); ApplyRemoteEvent(UpdateRemoteEvent( Doc("foo/bar", 2, Map("foo", "bar", "it", "base")), {target_id}, {})); @@ -731,43 +714,39 @@ TEST_P(LocalStoreTest, HandlesDocumentThenDeletedDocumentThenDocument) { TEST_P(LocalStoreTest, HandlesSetMutationThenPatchMutationThenDocumentThenAckThenAck) { WriteMutation(testutil::SetMutation("foo/bar", Map("foo", "old"))); - FSTAssertChanged( - Doc("foo/bar", 0, Map("foo", "old"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "old")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 0, Map("foo", "old"), DocumentState::kLocalMutations)); + Doc("foo/bar", 0, Map("foo", "old")).SetHasLocalMutations()); WriteMutation(testutil::PatchMutation("foo/bar", Map("foo", "bar"), {})); - FSTAssertChanged( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); core::Query query = Query("foo"); TargetId target_id = AllocateQuery(query); ApplyRemoteEvent( UpdateRemoteEvent(Doc("foo/bar", 1, Map("it", "base")), {target_id}, {})); - FSTAssertChanged( - Doc("foo/bar", 1, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 1, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 1, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 1, Map("foo", "bar")).SetHasLocalMutations()); local_store_.ReleaseTarget(target_id); AcknowledgeMutationWithVersion(2); // delete mutation - FSTAssertChanged( - Doc("foo/bar", 2, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 2, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 2, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 2, Map("foo", "bar")).SetHasLocalMutations()); AcknowledgeMutationWithVersion(3); // patch mutation FSTAssertChanged( - Doc("foo/bar", 3, Map("foo", "bar"), DocumentState::kCommittedMutations)); + Doc("foo/bar", 3, Map("foo", "bar")).SetHasCommittedMutations()); if (IsGcEager()) { // we've ack'd all of the mutations, nothing is keeping this pinned anymore FSTAssertNotContains("foo/bar"); } else { - FSTAssertContains(Doc("foo/bar", 3, Map("foo", "bar"), - DocumentState::kCommittedMutations)); + FSTAssertContains( + Doc("foo/bar", 3, Map("foo", "bar")).SetHasCommittedMutations()); } } @@ -775,10 +754,9 @@ TEST_P(LocalStoreTest, HandlesSetMutationAndPatchMutationTogether) { WriteMutations({testutil::SetMutation("foo/bar", Map("foo", "old")), testutil::PatchMutation("foo/bar", Map("foo", "bar"), {})}); - FSTAssertChanged( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); } TEST_P(LocalStoreTest, HandlesSetMutationThenPatchMutationThenReject) { @@ -786,7 +764,7 @@ TEST_P(LocalStoreTest, HandlesSetMutationThenPatchMutationThenReject) { WriteMutation(testutil::SetMutation("foo/bar", Map("foo", "old"))); FSTAssertContains( - Doc("foo/bar", 0, Map("foo", "old"), DocumentState::kLocalMutations)); + Doc("foo/bar", 0, Map("foo", "old")).SetHasLocalMutations()); AcknowledgeMutationWithVersion(1); FSTAssertNotContains("foo/bar"); @@ -803,13 +781,12 @@ TEST_P(LocalStoreTest, HandlesSetMutationsAndPatchMutationOfJustOneTogether) { testutil::SetMutation("bar/baz", Map("bar", "baz")), testutil::PatchMutation("foo/bar", Map("foo", "bar"), {})}); - FSTAssertChanged( - Doc("bar/baz", 0, Map("bar", "baz"), DocumentState::kLocalMutations), - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("bar/baz", 0, Map("bar", "baz")).SetHasLocalMutations(), + Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bar", 0, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("bar/baz", 0, Map("bar", "baz"), DocumentState::kLocalMutations)); + Doc("bar/baz", 0, Map("bar", "baz")).SetHasLocalMutations()); } TEST_P(LocalStoreTest, HandlesDeleteMutationThenPatchMutationThenAckThenAck) { @@ -823,8 +800,7 @@ TEST_P(LocalStoreTest, HandlesDeleteMutationThenPatchMutationThenAckThenAck) { AcknowledgeMutationWithVersion(2); // delete mutation FSTAssertRemoved("foo/bar"); - FSTAssertContains( - DeletedDoc("foo/bar", 2, /* has_committed_mutations= */ true)); + FSTAssertContains(DeletedDoc("foo/bar", 2).SetHasCommittedMutations()); AcknowledgeMutationWithVersion(3); // patch mutation FSTAssertChanged(UnknownDoc("foo/bar", 3)); @@ -880,15 +856,15 @@ TEST_P(LocalStoreTest, CollectsGarbageAfterAcknowledgedMutation) { WriteMutation(testutil::SetMutation("foo/bah", Map("foo", "bah"))); WriteMutation(testutil::DeleteMutation("foo/baz")); FSTAssertContains( - Doc("foo/bar", 1, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 1, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bah", 0, Map("foo", "bah"), DocumentState::kLocalMutations)); + Doc("foo/bah", 0, Map("foo", "bah")).SetHasLocalMutations()); FSTAssertContains(DeletedDoc("foo/baz")); AcknowledgeMutationWithVersion(3); FSTAssertNotContains("foo/bar"); FSTAssertContains( - Doc("foo/bah", 0, Map("foo", "bah"), DocumentState::kLocalMutations)); + Doc("foo/bah", 0, Map("foo", "bah")).SetHasLocalMutations()); FSTAssertContains(DeletedDoc("foo/baz")); AcknowledgeMutationWithVersion(4); @@ -918,15 +894,15 @@ TEST_P(LocalStoreTest, CollectsGarbageAfterRejectedMutation) { WriteMutation(testutil::SetMutation("foo/bah", Map("foo", "bah"))); WriteMutation(testutil::DeleteMutation("foo/baz")); FSTAssertContains( - Doc("foo/bar", 1, Map("foo", "bar"), DocumentState::kLocalMutations)); + Doc("foo/bar", 1, Map("foo", "bar")).SetHasLocalMutations()); FSTAssertContains( - Doc("foo/bah", 0, Map("foo", "bah"), DocumentState::kLocalMutations)); + Doc("foo/bah", 0, Map("foo", "bah")).SetHasLocalMutations()); FSTAssertContains(DeletedDoc("foo/baz")); RejectMutation(); // patch mutation FSTAssertNotContains("foo/bar"); FSTAssertContains( - Doc("foo/bah", 0, Map("foo", "bah"), DocumentState::kLocalMutations)); + Doc("foo/bah", 0, Map("foo", "bah")).SetHasLocalMutations()); FSTAssertContains(DeletedDoc("foo/baz")); RejectMutation(); // set mutation @@ -951,7 +927,7 @@ TEST_P(LocalStoreTest, PinsDocumentsInTheLocalView) { WriteMutation(testutil::SetMutation("foo/baz", Map("foo", "baz"))); FSTAssertContains(Doc("foo/bar", 1, Map("foo", "bar"))); FSTAssertContains( - Doc("foo/baz", 0, Map("foo", "baz"), DocumentState::kLocalMutations)); + Doc("foo/baz", 0, Map("foo", "baz")).SetHasLocalMutations()); NotifyLocalViewChanges(TestViewChanges(target_id, /* from_cache= */ false, {"foo/bar", "foo/baz"}, {})); @@ -961,7 +937,7 @@ TEST_P(LocalStoreTest, PinsDocumentsInTheLocalView) { ApplyRemoteEvent( UpdateRemoteEvent(Doc("foo/baz", 2, Map("foo", "baz")), {target_id}, {})); FSTAssertContains( - Doc("foo/baz", 2, Map("foo", "baz"), DocumentState::kLocalMutations)); + Doc("foo/baz", 2, Map("foo", "baz")).SetHasLocalMutations()); AcknowledgeMutationWithVersion(2); FSTAssertContains(Doc("foo/baz", 2, Map("foo", "baz"))); FSTAssertContains(Doc("foo/bar", 1, Map("foo", "bar"))); @@ -993,8 +969,8 @@ TEST_P(LocalStoreTest, CanExecuteDocumentQueries) { core::Query query = Query("foo/bar"); QueryResult query_result = ExecuteQuery(query); ASSERT_EQ(DocMapToVector(query_result.documents()), - Vector(Doc("foo/bar", 0, Map("foo", "bar"), - DocumentState::kLocalMutations))); + Vector(Document{ + Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()})); } TEST_P(LocalStoreTest, CanExecuteCollectionQueries) { @@ -1006,11 +982,12 @@ TEST_P(LocalStoreTest, CanExecuteCollectionQueries) { testutil::SetMutation("fooo/blah", Map("fooo", "blah"))}); core::Query query = Query("foo"); QueryResult query_result = ExecuteQuery(query); - ASSERT_EQ(DocMapToVector(query_result.documents()), - Vector(Doc("foo/bar", 0, Map("foo", "bar"), - DocumentState::kLocalMutations), - Doc("foo/baz", 0, Map("foo", "baz"), - DocumentState::kLocalMutations))); + ASSERT_EQ( + DocMapToVector(query_result.documents()), + Vector( + Document{Doc("foo/bar", 0, Map("foo", "bar")).SetHasLocalMutations()}, + Document{ + Doc("foo/baz", 0, Map("foo", "baz")).SetHasLocalMutations()})); } TEST_P(LocalStoreTest, CanExecuteMixedCollectionQueries) { @@ -1029,8 +1006,9 @@ TEST_P(LocalStoreTest, CanExecuteMixedCollectionQueries) { ASSERT_EQ( DocMapToVector(query_result.documents()), Vector( - Doc("foo/bar", 20, Map("a", "b")), Doc("foo/baz", 10, Map("a", "b")), - Doc("foo/bonk", 0, Map("a", "b"), DocumentState::kLocalMutations))); + Document{Doc("foo/bar", 20, Map("a", "b"))}, + Document{Doc("foo/baz", 10, Map("a", "b"))}, + Document{Doc("foo/bonk", 0, Map("a", "b")).SetHasLocalMutations()})); } TEST_P(LocalStoreTest, ReadsAllDocumentsForInitialCollectionQueries) { @@ -1108,24 +1086,18 @@ TEST_P(LocalStoreTest, RemoteDocumentKeysForTarget) { TEST_P(LocalStoreTest, HandlesSetMutationThenTransformThenTransform) { WriteMutation(testutil::SetMutation("foo/bar", Map("sum", 0))); - FSTAssertContains( - Doc("foo/bar", 0, Map("sum", 0), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 0, Map("sum", 0), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 0, Map("sum", 0)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 0)).SetHasLocalMutations()); WriteMutation(testutil::PatchMutation( "foo/bar", Map(), {testutil::Increment("sum", Value(1))})); - FSTAssertContains( - Doc("foo/bar", 0, Map("sum", 1), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 0, Map("sum", 1), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 0, Map("sum", 1)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 1)).SetHasLocalMutations()); WriteMutation(testutil::PatchMutation( "foo/bar", Map(), {testutil::Increment("sum", Value(2))})); - FSTAssertContains( - Doc("foo/bar", 0, Map("sum", 3), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 0, Map("sum", 3), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 0, Map("sum", 3)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 3)).SetHasLocalMutations()); } TEST_P(LocalStoreTest, @@ -1136,36 +1108,28 @@ TEST_P(LocalStoreTest, if (IsGcEager()) return; WriteMutation(testutil::SetMutation("foo/bar", Map("sum", 0))); - FSTAssertContains( - Doc("foo/bar", 0, Map("sum", 0), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 0, Map("sum", 0), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 0, Map("sum", 0)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 0)).SetHasLocalMutations()); AcknowledgeMutationWithVersion(1); FSTAssertContains( - Doc("foo/bar", 1, Map("sum", 0), DocumentState::kCommittedMutations)); - FSTAssertChanged( - Doc("foo/bar", 1, Map("sum", 0), DocumentState::kCommittedMutations)); + Doc("foo/bar", 1, Map("sum", 0)).SetHasCommittedMutations()); + FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 0)).SetHasCommittedMutations()); WriteMutation(testutil::PatchMutation( "foo/bar", Map(), {testutil::Increment("sum", Value(1))})); - FSTAssertContains( - Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 1, Map("sum", 1)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1)).SetHasLocalMutations()); AcknowledgeMutationWithVersion(2, Value(1)); FSTAssertContains( - Doc("foo/bar", 2, Map("sum", 1), DocumentState::kCommittedMutations)); - FSTAssertChanged( - Doc("foo/bar", 2, Map("sum", 1), DocumentState::kCommittedMutations)); + Doc("foo/bar", 2, Map("sum", 1)).SetHasCommittedMutations()); + FSTAssertChanged(Doc("foo/bar", 2, Map("sum", 1)).SetHasCommittedMutations()); WriteMutation(testutil::PatchMutation( "foo/bar", Map(), {testutil::Increment("sum", Value(2))})); - FSTAssertContains( - Doc("foo/bar", 2, Map("sum", 3), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 2, Map("sum", 3), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 2, Map("sum", 3)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 2, Map("sum", 3)).SetHasLocalMutations()); } TEST_P(LocalStoreTest, UsesTargetMappingToExecuteQueries) { @@ -1345,10 +1309,8 @@ TEST_P(LocalStoreTest, FSTAssertTargetID(2); WriteMutation(testutil::SetMutation("foo/bar", Map("sum", 0))); - FSTAssertContains( - Doc("foo/bar", 0, Map("sum", 0), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 0, Map("sum", 0), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 0, Map("sum", 0)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 0)).SetHasLocalMutations()); ApplyRemoteEvent(AddedRemoteEvent(Doc("foo/bar", 1, Map("sum", 0)), {2})); @@ -1358,40 +1320,32 @@ TEST_P(LocalStoreTest, WriteMutation(testutil::PatchMutation( "foo/bar", Map(), {testutil::Increment("sum", Value(1))})); - FSTAssertContains( - Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 1, Map("sum", 1)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1)).SetHasLocalMutations()); // The value in this remote event gets ignored since we still have a pending // transform mutation. ApplyRemoteEvent( UpdateRemoteEvent(Doc("foo/bar", 2, Map("sum", 0)), {2}, {})); - FSTAssertContains( - Doc("foo/bar", 2, Map("sum", 1), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 2, Map("sum", 1), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 2, Map("sum", 1)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 2, Map("sum", 1)).SetHasLocalMutations()); // Add another increment. Note that we still compute the increment based on // the local value. WriteMutation(testutil::PatchMutation( "foo/bar", Map(), {testutil::Increment("sum", Value(2))})); - FSTAssertContains( - Doc("foo/bar", 2, Map("sum", 3), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 2, Map("sum", 3), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 2, Map("sum", 3)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 2, Map("sum", 3)).SetHasLocalMutations()); AcknowledgeMutationWithVersion(3, Value(1)); - FSTAssertContains( - Doc("foo/bar", 3, Map("sum", 3), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 3, Map("sum", 3), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 3, Map("sum", 3)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 3, Map("sum", 3)).SetHasLocalMutations()); AcknowledgeMutationWithVersion(4, Value(1339)); FSTAssertContains( - Doc("foo/bar", 4, Map("sum", 1339), DocumentState::kCommittedMutations)); + Doc("foo/bar", 4, Map("sum", 1339)).SetHasCommittedMutations()); FSTAssertChanged( - Doc("foo/bar", 4, Map("sum", 1339), DocumentState::kCommittedMutations)); + Doc("foo/bar", 4, Map("sum", 1339)).SetHasCommittedMutations()); } TEST_P(LocalStoreTest, HoldsBackOnlyNonIdempotentTransforms) { @@ -1401,27 +1355,28 @@ TEST_P(LocalStoreTest, HoldsBackOnlyNonIdempotentTransforms) { WriteMutation( testutil::SetMutation("foo/bar", Map("sum", 0, "array_union", Array()))); - FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 0, "array_union", Array()), - DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 0, "array_union", Array())) + .SetHasLocalMutations()); AcknowledgeMutationWithVersion(1); - FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 0, "array_union", Array()), - DocumentState::kCommittedMutations)); + FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 0, "array_union", Array())) + .SetHasCommittedMutations()); ApplyRemoteEvent(AddedRemoteEvent( Doc("foo/bar", 1, Map("sum", 0, "array_union", Array())), {2})); FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 0, "array_union", Array()))); + std::vector> array_union; + array_union.push_back(Value("foo")); WriteMutations({ testutil::PatchMutation("foo/bar", Map(), {testutil::Increment("sum", Value(1))}), testutil::PatchMutation( - "foo/bar", Map(), - {testutil::ArrayUnion("array_union", {Value("foo")})}), + "foo/bar", Map(), {testutil::ArrayUnion("array_union", array_union)}), }); - FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1, "array_union", Array("foo")), - DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1, "array_union", Array("foo"))) + .SetHasLocalMutations()); // The sum transform is not idempotent and the backend's updated value is // ignored. The ArrayUnion transform is recomputed and includes the backend @@ -1429,9 +1384,9 @@ TEST_P(LocalStoreTest, HoldsBackOnlyNonIdempotentTransforms) { ApplyRemoteEvent(UpdateRemoteEvent( Doc("foo/bar", 2, Map("sum", 1337, "array_union", Array("bar"))), {2}, {})); - FSTAssertChanged(Doc("foo/bar", 2, - Map("sum", 1, "array_union", Array("bar", "foo")), - DocumentState::kLocalMutations)); + FSTAssertChanged( + Doc("foo/bar", 2, Map("sum", 1, "array_union", Array("bar", "foo"))) + .SetHasLocalMutations()); } TEST_P(LocalStoreTest, HandlesMergeMutationWithTransformThenRemoteEvent) { @@ -1443,17 +1398,13 @@ TEST_P(LocalStoreTest, HandlesMergeMutationWithTransformThenRemoteEvent) { testutil::MergeMutation("foo/bar", Map(), std::vector(), {testutil::Increment("sum", Value(1))})); - FSTAssertContains( - Doc("foo/bar", 0, Map("sum", 1), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 0, Map("sum", 1), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 0, Map("sum", 1)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 1)).SetHasLocalMutations()); ApplyRemoteEvent(AddedRemoteEvent(Doc("foo/bar", 1, Map("sum", 1337)), {2})); - FSTAssertContains( - Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 1, Map("sum", 1)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1)).SetHasLocalMutations()); } TEST_P(LocalStoreTest, HandlesPatchMutationWithTransformThenRemoteEvent) { @@ -1471,10 +1422,8 @@ TEST_P(LocalStoreTest, HandlesPatchMutationWithTransformThenRemoteEvent) { // replay the mutation once we receive the first value from the remote event. ApplyRemoteEvent(AddedRemoteEvent(Doc("foo/bar", 1, Map("sum", 1337)), {2})); - FSTAssertContains( - Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 1, Map("sum", 1)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1)).SetHasLocalMutations()); } TEST_P(LocalStoreTest, HandlesSavingBundledDocuments) { @@ -1553,16 +1502,12 @@ TEST_P(LocalStoreTest, testutil::MergeMutation("foo/bar", Map(), std::vector(), {testutil::Increment("sum", Value(1))})); - FSTAssertContains( - Doc("foo/bar", 0, Map("sum", 1), DocumentState::kLocalMutations)); - FSTAssertChanged( - Doc("foo/bar", 0, Map("sum", 1), DocumentState::kLocalMutations)); + FSTAssertContains(Doc("foo/bar", 0, Map("sum", 1)).SetHasLocalMutations()); + FSTAssertChanged(Doc("foo/bar", 0, Map("sum", 1)).SetHasLocalMutations()); ApplyBundledDocuments({Doc("foo/bar", 1, Map("sum", 1337))}); - FSTAssertChanged( - Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations)); - FSTAssertContains( - Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1)).SetHasLocalMutations()); + FSTAssertContains(Doc("foo/bar", 1, Map("sum", 1)).SetHasLocalMutations()); DocumentKeySet expected_keys({Key("foo/bar")}); FSTAssertQueryDocumentMapping(4, expected_keys); @@ -1582,10 +1527,8 @@ TEST_P(LocalStoreTest, FSTAssertChanged(DeletedDoc("foo/bar")); ApplyBundledDocuments({Doc("foo/bar", 1, Map("sum", 1337))}); - FSTAssertChanged( - Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations)); - FSTAssertContains( - Doc("foo/bar", 1, Map("sum", 1), DocumentState::kLocalMutations)); + FSTAssertChanged(Doc("foo/bar", 1, Map("sum", 1)).SetHasLocalMutations()); + FSTAssertContains(Doc("foo/bar", 1, Map("sum", 1)).SetHasLocalMutations()); DocumentKeySet expected_keys({Key("foo/bar")}); FSTAssertQueryDocumentMapping(4, expected_keys); diff --git a/Firestore/core/test/unit/local/local_store_test.h b/Firestore/core/test/unit/local/local_store_test.h index 265f667d0c5..f8344765dfa 100644 --- a/Firestore/core/test/unit/local/local_store_test.h +++ b/Firestore/core/test/unit/local/local_store_test.h @@ -24,7 +24,6 @@ #include "Firestore/core/src/local/local_store.h" #include "Firestore/core/src/local/query_engine.h" #include "Firestore/core/src/local/query_result.h" -#include "Firestore/core/src/model/document_map.h" #include "Firestore/core/src/model/mutation_batch.h" #include "Firestore/core/test/unit/local/counting_query_engine.h" #include "gtest/gtest.h" @@ -89,13 +88,14 @@ class LocalStoreTest : public ::testing::TestWithParam { void NotifyLocalViewChanges(LocalViewChanges changes); void AcknowledgeMutationWithVersion( int64_t document_version, - absl::optional transform_result = absl::nullopt); + absl::optional> + transform_result = absl::nullopt); void RejectMutation(); model::TargetId AllocateQuery(core::Query query); local::TargetData GetTargetData(const core::Query& query); local::QueryResult ExecuteQuery(const core::Query& query); void ApplyBundledDocuments( - const std::vector& documents); + const std::vector& documents); /** * Applies the `from_cache` state to the given target via a synthesized @@ -109,7 +109,7 @@ class LocalStoreTest : public ::testing::TestWithParam { CountingQueryEngine query_engine_; LocalStore local_store_; std::vector batches_; - model::MaybeDocumentMap last_changes_; + model::DocumentMap last_changes_; model::TargetId last_target_id_ = 0; local::QueryResult last_query_result_; diff --git a/Firestore/core/test/unit/local/lru_garbage_collector_test.cc b/Firestore/core/test/unit/local/lru_garbage_collector_test.cc index e9a5241d1eb..318b3802e5e 100644 --- a/Firestore/core/test/unit/local/lru_garbage_collector_test.cc +++ b/Firestore/core/test/unit/local/lru_garbage_collector_test.cc @@ -31,8 +31,8 @@ #include "Firestore/core/src/local/remote_document_cache.h" #include "Firestore/core/src/local/target_cache.h" #include "Firestore/core/src/local/target_data.h" -#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/document_key_set.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/mutation.h" #include "Firestore/core/src/model/mutation_batch.h" #include "Firestore/core/src/model/precondition.h" @@ -49,12 +49,11 @@ namespace firestore { namespace local { using auth::User; -using model::Document; using model::DocumentKey; using model::DocumentKeyHash; using model::DocumentKeySet; -using model::DocumentState; using model::ListenSequenceNumber; +using model::MutableDocument; using model::Mutation; using model::ObjectValue; using model::Precondition; @@ -199,8 +198,8 @@ void LruGarbageCollectorTest::RemoveDocument(const DocumentKey& doc_key, target_cache_->RemoveMatchingKeys(DocumentKeySet{doc_key}, target_id); } -Document LruGarbageCollectorTest::CacheADocumentInTransaction() { - Document doc = NextTestDocument(); +MutableDocument LruGarbageCollectorTest::CacheADocumentInTransaction() { + MutableDocument doc = NextTestDocument(); document_cache_->Add(doc, doc.version()); return doc; } @@ -214,13 +213,13 @@ DocumentKey LruGarbageCollectorTest::NextTestDocKey() { return Key("docs/doc_" + std::to_string(++previous_doc_num_)); } -Document LruGarbageCollectorTest::NextTestDocumentWithValue(ObjectValue value) { +MutableDocument LruGarbageCollectorTest::NextTestDocumentWithValue( + ObjectValue value) { DocumentKey key = NextTestDocKey(); - return Document(std::move(value), std::move(key), Version(2), - DocumentState::kSynced); + return MutableDocument::FoundDocument(key, Version(2), std::move(value)); } -Document LruGarbageCollectorTest::NextTestDocument() { +MutableDocument LruGarbageCollectorTest::NextTestDocument() { return NextTestDocumentWithValue(test_value_); } @@ -331,7 +330,7 @@ TEST_P(LruGarbageCollectorTest, SequenceNumbersWithMutationsInQueries) { // GC'd. Expect 3 past the initial value: the mutations not part of a query, // and two queries. NewTestResources(); - Document doc_in_query = NextTestDocument(); + MutableDocument doc_in_query = NextTestDocument(); persistence_->Run("mark mutations", [&] { // Adding 9 doc keys in a transaction. If we remove one of them, we'll have // room for two actual queries. @@ -413,11 +412,11 @@ TEST_P(LruGarbageCollectorTest, RemoveOrphanedDocuments) { // Add two documents to first target, queue a mutation on the second // document. TargetData target_data = AddNextQueryInTransaction(); - Document doc1 = CacheADocumentInTransaction(); + MutableDocument doc1 = CacheADocumentInTransaction(); AddDocument(doc1.key(), target_data.target_id()); expected_retained.insert(doc1.key()); - Document doc2 = CacheADocumentInTransaction(); + MutableDocument doc2 = CacheADocumentInTransaction(); AddDocument(doc2.key(), target_data.target_id()); expected_retained.insert(doc2.key()); mutations.push_back(MutationForDocument(doc2.key())); @@ -426,14 +425,14 @@ TEST_P(LruGarbageCollectorTest, RemoveOrphanedDocuments) { // Add a second query and register a third document on it. persistence_->Run("second query", [&] { TargetData target_data = AddNextQueryInTransaction(); - Document doc3 = CacheADocumentInTransaction(); + MutableDocument doc3 = CacheADocumentInTransaction(); expected_retained.insert(doc3.key()); AddDocument(doc3.key(), target_data.target_id()); }); // Cache another document and prepare a mutation on it. persistence_->Run("queue a mutation", [&] { - Document doc4 = CacheADocumentInTransaction(); + MutableDocument doc4 = CacheADocumentInTransaction(); mutations.push_back(MutationForDocument(doc4.key())); expected_retained.insert(doc4.key()); }); @@ -452,7 +451,7 @@ TEST_P(LruGarbageCollectorTest, RemoveOrphanedDocuments) { std::unordered_set to_be_removed; persistence_->Run("add orphaned docs (previously mutated, then ack'd)", [&] { for (int i = 0; i < 5; i++) { - Document doc = CacheADocumentInTransaction(); + MutableDocument doc = CacheADocumentInTransaction(); to_be_removed.insert(doc.key()); MarkDocumentEligibleForGcInTransaction(doc.key()); } @@ -464,11 +463,11 @@ TEST_P(LruGarbageCollectorTest, RemoveOrphanedDocuments) { ASSERT_EQ(to_be_removed.size(), removed); persistence_->Run("verify", [&] { for (const DocumentKey& key : to_be_removed) { - ASSERT_EQ(document_cache_->Get(key), absl::nullopt); + ASSERT_FALSE(document_cache_->Get(key).is_valid_document()); ASSERT_FALSE(target_cache_->Contains(key)); } for (const DocumentKey& key : expected_retained) { - ASSERT_NE(document_cache_->Get(key), absl::nullopt) + ASSERT_TRUE(document_cache_->Get(key).is_valid_document()) << "Missing document " << key.ToString().c_str(); } }); @@ -513,7 +512,7 @@ TEST_P(LruGarbageCollectorTest, RemoveTargetsThenGC) { persistence_->Run("Add oldest target and docs", [&] { TargetData target_data = AddNextQueryInTransaction(); for (int i = 0; i < 5; i++) { - Document doc = CacheADocumentInTransaction(); + MutableDocument doc = CacheADocumentInTransaction(); expected_retained.insert(doc.key()); AddDocument(doc.key(), target_data.target_id()); } @@ -534,7 +533,7 @@ TEST_P(LruGarbageCollectorTest, RemoveTargetsThenGC) { // to their sequence numbers. Since they will not be a part of the // target, we expect them to be removed. for (int i = 0; i < 2; i++) { - Document doc = CacheADocumentInTransaction(); + MutableDocument doc = CacheADocumentInTransaction(); expected_removed.insert(doc.key()); AddDocument(doc.key(), middle_target.target_id()); middle_docs_to_remove = middle_docs_to_remove.insert(doc.key()); @@ -544,14 +543,14 @@ TEST_P(LruGarbageCollectorTest, RemoveTargetsThenGC) { // in this target prevents them from being GC'd, so they are also // expected to be retained. for (int i = 2; i < 4; i++) { - Document doc = CacheADocumentInTransaction(); + MutableDocument doc = CacheADocumentInTransaction(); expected_retained.insert(doc.key()); AddDocument(doc.key(), middle_target.target_id()); } // This doc stays in this target, but gets updated. { - Document doc = CacheADocumentInTransaction(); + MutableDocument doc = CacheADocumentInTransaction(); expected_retained.insert(doc.key()); AddDocument(doc.key(), middle_target.target_id()); middle_doc_to_update = doc.key(); @@ -570,7 +569,7 @@ TEST_P(LruGarbageCollectorTest, RemoveTargetsThenGC) { // These documents are only in this target. They are expected to be removed // because this target will also be removed. for (int i = 0; i < 3; i++) { - Document doc = CacheADocumentInTransaction(); + MutableDocument doc = CacheADocumentInTransaction(); expected_removed.insert(doc.key()); AddDocument(doc.key(), newest_target.target_id()); } @@ -578,7 +577,7 @@ TEST_P(LruGarbageCollectorTest, RemoveTargetsThenGC) { // Docs to add to the oldest target in addition to this target. They will be // retained. for (int i = 3; i < 5; i++) { - Document doc = CacheADocumentInTransaction(); + MutableDocument doc = CacheADocumentInTransaction(); expected_retained.insert(doc.key()); AddDocument(doc.key(), newest_target.target_id()); newest_docs_to_add_to_oldest = @@ -591,14 +590,14 @@ TEST_P(LruGarbageCollectorTest, RemoveTargetsThenGC) { // Write two docs and have them ack'd by the server. Can skip mutation queue // and set them in document cache. Add potentially orphaned first, also add // one doc to a target. - Document doc1 = CacheADocumentInTransaction(); + MutableDocument doc1 = CacheADocumentInTransaction(); MarkDocumentEligibleForGcInTransaction(doc1.key()); UpdateTargetInTransaction(oldest_target); AddDocument(doc1.key(), oldest_target.target_id()); // doc1 should be retained by being added to oldest_target. expected_retained.insert(doc1.key()); - Document doc2 = CacheADocumentInTransaction(); + MutableDocument doc2 = CacheADocumentInTransaction(); MarkDocumentEligibleForGcInTransaction(doc2.key()); // Nothing is keeping doc2 around, it should be removed. expected_removed.insert(doc2.key()); @@ -627,8 +626,8 @@ TEST_P(LruGarbageCollectorTest, RemoveTargetsThenGC) { // Update a doc in the middle target persistence_->Run("Update a doc in the middle target", [&] { int64_t version = 3; - Document doc(ObjectValue(test_value_), middle_doc_to_update, - Version(version), DocumentState::kSynced); + MutableDocument doc = MutableDocument::FoundDocument( + middle_doc_to_update, Version(version), ObjectValue(test_value_)); document_cache_->Add(doc, doc.version()); UpdateTargetInTransaction(middle_target); }); @@ -637,7 +636,7 @@ TEST_P(LruGarbageCollectorTest, RemoveTargetsThenGC) { // Write a doc and get an ack, not part of a target. persistence_->Run("Write a doc and get an ack, not part of a target", [&] { - Document doc = CacheADocumentInTransaction(); + MutableDocument doc = CacheADocumentInTransaction(); // Mark it as eligible for GC, but this is after our upper bound for what we // will collect. MarkDocumentEligibleForGcInTransaction(doc.key()); @@ -656,7 +655,7 @@ TEST_P(LruGarbageCollectorTest, RemoveTargetsThenGC) { ASSERT_EQ(expected_removed.size(), docs_removed); persistence_->Run("verify results", [&] { for (const DocumentKey& key : expected_removed) { - ASSERT_EQ(document_cache_->Get(key), absl::nullopt) + ASSERT_FALSE(document_cache_->Get(key).is_valid_document()) << "Did not expect to find " << key.ToString().c_str() << "in document cache"; ASSERT_FALSE(target_cache_->Contains(key)) @@ -665,7 +664,7 @@ TEST_P(LruGarbageCollectorTest, RemoveTargetsThenGC) { ExpectSentinelRemoved(key); } for (const DocumentKey& key : expected_retained) { - ASSERT_NE(document_cache_->Get(key), absl::nullopt) + ASSERT_TRUE(document_cache_->Get(key).is_valid_document()) << "Expected to find " << key.ToString().c_str() << " in document cache"; } @@ -682,7 +681,7 @@ TEST_P(LruGarbageCollectorTest, GetsSize) { persistence_->Run("fill cache", [&] { // Simulate a bunch of ack'd mutations. for (int i = 0; i < 50; i++) { - Document doc = CacheADocumentInTransaction(); + MutableDocument doc = CacheADocumentInTransaction(); MarkDocumentEligibleForGcInTransaction(doc.key()); } }); @@ -700,7 +699,7 @@ TEST_P(LruGarbageCollectorTest, Disabled) { persistence_->Run("fill cache", [&] { // Simulate a bunch of ack'd mutations. for (int i = 0; i < 500; i++) { - Document doc = CacheADocumentInTransaction(); + MutableDocument doc = CacheADocumentInTransaction(); MarkDocumentEligibleForGcInTransaction(doc.key()); } }); @@ -717,7 +716,7 @@ TEST_P(LruGarbageCollectorTest, CacheTooSmall) { persistence_->Run("fill cache", [&] { // Simulate a bunch of ack'd mutations. for (int i = 0; i < 50; i++) { - Document doc = CacheADocumentInTransaction(); + MutableDocument doc = CacheADocumentInTransaction(); MarkDocumentEligibleForGcInTransaction(doc.key()); } }); @@ -747,7 +746,7 @@ TEST_P(LruGarbageCollectorTest, GCRan) { persistence_->Run("Add a target and some documents", [&] { TargetData target_data = AddNextQueryInTransaction(); for (int j = 0; j < 10; j++) { - Document doc = CacheADocumentInTransaction(); + MutableDocument doc = CacheADocumentInTransaction(); AddDocument(doc.key(), target_data.target_id()); } }); diff --git a/Firestore/core/test/unit/local/lru_garbage_collector_test.h b/Firestore/core/test/unit/local/lru_garbage_collector_test.h index 5e05167a45f..fda275d61de 100644 --- a/Firestore/core/test/unit/local/lru_garbage_collector_test.h +++ b/Firestore/core/test/unit/local/lru_garbage_collector_test.h @@ -23,7 +23,7 @@ #include "Firestore/core/src/auth/user.h" #include "Firestore/core/src/local/reference_set.h" #include "Firestore/core/src/local/target_data.h" -#include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/model/object_value.h" #include "Firestore/core/src/model/types.h" #include "gtest/gtest.h" @@ -182,7 +182,7 @@ class LruGarbageCollectorTest : public ::testing::TestWithParam { * - added to a target * - now has or previously had a pending mutation */ - model::Document CacheADocumentInTransaction(); + model::MutableDocument CacheADocumentInTransaction(); /** * Returns a new arbitrary, unsaved mutation for the document named by @@ -194,10 +194,10 @@ class LruGarbageCollectorTest : public ::testing::TestWithParam { model::DocumentKey NextTestDocKey(); /** Returns a new, unsaved document with arbitrary contents. */ - model::Document NextTestDocument(); + model::MutableDocument NextTestDocument(); /** Returns a new, unsaved document with the given contents. */ - model::Document NextTestDocumentWithValue(model::ObjectValue value); + model::MutableDocument NextTestDocumentWithValue(model::ObjectValue value); std::unique_ptr test_helper_; diff --git a/Firestore/core/test/unit/local/persistence_testing.cc b/Firestore/core/test/unit/local/persistence_testing.cc index 9d715b96923..1d60cd48ca1 100644 --- a/Firestore/core/test/unit/local/persistence_testing.cc +++ b/Firestore/core/test/unit/local/persistence_testing.cc @@ -42,13 +42,13 @@ using util::Filesystem; using util::Path; using util::Status; +} // namespace + LocalSerializer MakeLocalSerializer() { Serializer remote_serializer{DatabaseId("p", "d")}; return LocalSerializer(std::move(remote_serializer)); } -} // namespace - Path LevelDbDir() { auto* fs = Filesystem::Default(); Path dir = fs->TempDir().AppendUtf8("PersistenceTesting"); diff --git a/Firestore/core/test/unit/local/persistence_testing.h b/Firestore/core/test/unit/local/persistence_testing.h index 70db39927a0..516368f46be 100644 --- a/Firestore/core/test/unit/local/persistence_testing.h +++ b/Firestore/core/test/unit/local/persistence_testing.h @@ -19,6 +19,8 @@ #include +#include "Firestore/core/src/local/local_serializer.h" + namespace firebase { namespace firestore { namespace util { @@ -33,6 +35,12 @@ class LevelDbPersistence; struct LruParams; class MemoryPersistence; +/** + * Returns a new instance of local serializer using the default testing + * database. + */ +local::LocalSerializer MakeLocalSerializer(); + /** * Returns the directory where a LevelDB instance can store data files during * testing. Any files that existed there will be deleted first. diff --git a/Firestore/core/test/unit/local/query_engine_test.cc b/Firestore/core/test/unit/local/query_engine_test.cc index 7a9f23ba847..c265856bc1e 100644 --- a/Firestore/core/test/unit/local/query_engine_test.cc +++ b/Firestore/core/test/unit/local/query_engine_test.cc @@ -49,12 +49,11 @@ using local::QueryEngine; using local::RemoteDocumentCache; using local::TargetCache; using model::BatchId; -using model::Document; using model::DocumentKey; using model::DocumentKeySet; using model::DocumentMap; using model::DocumentSet; -using model::DocumentState; +using model::MutableDocument; using model::SnapshotVersion; using model::TargetId; using testutil::Doc; @@ -68,23 +67,19 @@ using testutil::Version; const int kTestTargetId = 1; -const Document kMatchingDocA = +const MutableDocument kMatchingDocA = Doc("coll/a", 1, Map("matches", true, "order", 1)); -const Document kNonMatchingDocA = +const MutableDocument kNonMatchingDocA = Doc("coll/a", 1, Map("matches", false, "order", 1)); -const Document pPendingMatchingDocA = Doc("coll/a", - 1, - Map("matches", true, "order", 1), - DocumentState::kLocalMutations); -const Document kPendingNonMatchingDocA = Doc("coll/a", - 1, - Map("matches", false, "order", 1), - DocumentState::kLocalMutations); -const Document kUpdatedDocA = +const MutableDocument pPendingMatchingDocA = + Doc("coll/a", 1, Map("matches", true, "order", 1)).SetHasLocalMutations(); +const MutableDocument kPendingNonMatchingDocA = + Doc("coll/a", 1, Map("matches", false, "order", 1)).SetHasLocalMutations(); +const MutableDocument kUpdatedDocA = Doc("coll/a", 11, Map("matches", true, "order", 1)); -const Document kMatchingDocB = +const MutableDocument kMatchingDocB = Doc("coll/b", 1, Map("matches", true, "order", 2)); -const Document kUpdatedMatchingDocB = +const MutableDocument kUpdatedMatchingDocB = Doc("coll/b", 11, Map("matches", true, "order", 2)); const SnapshotVersion kLastLimboFreeSnapshot = Version(10); @@ -142,9 +137,9 @@ class QueryEngineTest : public ::testing::Test { } /** Adds the provided documents to the remote document cache. */ - void AddDocuments(const std::vector& docs) { + void AddDocuments(const std::vector& docs) { persistence_->Run("AddDocuments", [&] { - for (const Document& doc : docs) { + for (const MutableDocument& doc : docs) { remote_document_cache_->Add(doc, doc.version()); } }); @@ -170,7 +165,7 @@ class QueryEngineTest : public ::testing::Test { query, last_limbo_free_snapshot_version, remote_keys); View view(query, DocumentKeySet()); ViewDocumentChanges view_doc_changes = - view.ComputeDocumentChanges(docs.underlying_map(), {}); + view.ComputeDocumentChanges(docs, {}); return view.ApplyChanges(view_doc_changes).snapshot()->documents(); } @@ -370,8 +365,7 @@ TEST_F(QueryEngineTest, PersistQueryMapping({Key("coll/a"), Key("coll/b")}); // Update "coll/a" but make sure it still sorts before "coll/b" - AddDocuments( - {Doc("coll/a", 1, Map("order", 2), DocumentState::kLocalMutations)}); + AddDocuments({Doc("coll/a", 1, Map("order", 2)).SetHasLocalMutations()}); // Since the last document in the limit didn't change (and hence we know that // all documents written prior to query execution still sort after "coll/b"), @@ -379,9 +373,9 @@ TEST_F(QueryEngineTest, DocumentSet docs = ExpectOptimizedCollectionScan( [&] { return RunQuery(query, kLastLimboFreeSnapshot); }); EXPECT_EQ(docs, - DocSet(query.Comparator(), {Doc("coll/a", 1, Map("order", 2), - DocumentState::kLocalMutations), - Doc("coll/b", 1, Map("order", 3))})); + DocSet(query.Comparator(), + {Doc("coll/a", 1, Map("order", 2)).SetHasLocalMutations(), + Doc("coll/b", 1, Map("order", 3))})); } } // namespace local diff --git a/Firestore/core/test/unit/local/remote_document_cache_test.cc b/Firestore/core/test/unit/local/remote_document_cache_test.cc index 906362475e1..b0364d468c0 100644 --- a/Firestore/core/test/unit/local/remote_document_cache_test.cc +++ b/Firestore/core/test/unit/local/remote_document_cache_test.cc @@ -25,8 +25,9 @@ #include "Firestore/core/src/local/remote_document_cache.h" #include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/document_key_set.h" -#include "Firestore/core/src/model/document_map.h" -#include "Firestore/core/src/model/no_document.h" +#include "Firestore/core/src/model/object_value.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/util/string_apple.h" #include "Firestore/core/test/unit/testutil/testutil.h" #include "absl/strings/string_view.h" @@ -38,31 +39,32 @@ namespace firestore { namespace local { namespace { -using model::Document; +using model::DeepClone; using model::DocumentKey; using model::DocumentKeySet; using model::DocumentMap; -using model::DocumentState; -using model::FieldValue; -using model::MaybeDocument; -using model::MaybeDocumentMap; -using model::NoDocument; -using model::OptionalMaybeDocumentMap; +using model::MutableDocument; +using model::MutableDocumentMap; +using model::ObjectValue; using model::SnapshotVersion; +using nanopb::Message; +using testing::Eq; using testing::IsSupersetOf; using testing::Matches; using testing::UnorderedElementsAreArray; using testutil::DeletedDoc; using testutil::Doc; +using testutil::Field; +using testutil::Key; using testutil::Map; using testutil::Query; +using testutil::Value; using testutil::Version; const char* kDocPath = "a/b"; const char* kLongDocPath = "a/b/c/d/e/f"; const int kVersion = 42; -FieldValue::Map kDocData; /** * Extracts all the actual MaybeDocument instances from the given document map. @@ -71,10 +73,10 @@ FieldValue::Map kDocData; * MaybeDocumentMap. */ template -std::vector ExtractDocuments(const MapType& docs) { - std::vector result; +std::vector ExtractDocuments(const MapType& docs) { + std::vector result; for (const auto& kv : docs) { - const absl::optional& doc = kv.second; + const absl::optional& doc = kv.second; if (doc) { result.push_back(*doc); } @@ -85,14 +87,14 @@ std::vector ExtractDocuments(const MapType& docs) { MATCHER_P(HasExactlyDocs, expected, negation ? "missing docs" : "has exactly docs") { - std::vector arg_docs = ExtractDocuments(arg); + std::vector arg_docs = ExtractDocuments(arg); return testing::Value(arg_docs, UnorderedElementsAreArray(expected)); } MATCHER_P(HasAtLeastDocs, expected, negation ? "missing docs" : "has at least docs") { - std::vector arg_docs = ExtractDocuments(arg); + std::vector arg_docs = ExtractDocuments(arg); return testing::Value(arg_docs, IsSupersetOf(expected)); } @@ -101,13 +103,11 @@ MATCHER_P(HasAtLeastDocs, RemoteDocumentCacheTest::RemoteDocumentCacheTest() : persistence_{GetParam()()}, cache_{persistence_->remote_document_cache()} { - // essentially a constant, but can't be a compile-time one. - kDocData = Map("a", 1, "b", 2); } TEST_P(RemoteDocumentCacheTest, ReadDocumentNotInCache) { persistence_->Run("test_read_document_not_in_cache", [&] { - ASSERT_EQ(absl::nullopt, cache_->Get(testutil::Key(kDocPath))); + ASSERT_FALSE(cache_->Get(Key(kDocPath)).is_valid_document()); }); } @@ -117,12 +117,12 @@ TEST_P(RemoteDocumentCacheTest, SetAndReadADocument) { TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocuments) { persistence_->Run("test_set_and_read_several_documents", [=] { - std::vector written = { + std::vector written = { SetTestDocument(kDocPath), SetTestDocument(kLongDocPath), }; - OptionalMaybeDocumentMap read = cache_->GetAll( - DocumentKeySet{testutil::Key(kDocPath), testutil::Key(kLongDocPath)}); + MutableDocumentMap read = + cache_->GetAll(DocumentKeySet{Key(kDocPath), Key(kLongDocPath)}); EXPECT_THAT(read, HasExactlyDocs(written)); }); } @@ -131,19 +131,19 @@ TEST_P(RemoteDocumentCacheTest, SetAndReadSeveralDocumentsIncludingMissingDocument) { persistence_->Run( "test_set_and_read_several_documents_including_missing_document", [=] { - std::vector written = { + std::vector written = { SetTestDocument(kDocPath), SetTestDocument(kLongDocPath), }; - OptionalMaybeDocumentMap read = cache_->GetAll(DocumentKeySet{ - testutil::Key(kDocPath), - testutil::Key(kLongDocPath), - testutil::Key("foo/nonexistent"), + MutableDocumentMap read = cache_->GetAll(DocumentKeySet{ + Key(kDocPath), + Key(kLongDocPath), + Key("foo/nonexistent"), }); EXPECT_THAT(read, HasAtLeastDocs(written)); auto found = read.find(DocumentKey::FromPathString("foo/nonexistent")); ASSERT_TRUE(found != read.end()); - ASSERT_EQ(absl::nullopt, found->second); + ASSERT_FALSE(found->second.is_valid_document()); }); } @@ -153,36 +153,37 @@ TEST_P(RemoteDocumentCacheTest, SetAndReadADocumentAtDeepPath) { TEST_P(RemoteDocumentCacheTest, SetAndReadDeletedDocument) { persistence_->Run("test_set_and_read_deleted_document", [&] { - absl::optional deleted_doc = DeletedDoc(kDocPath, kVersion); + absl::optional deleted_doc = + DeletedDoc(kDocPath, kVersion); cache_->Add(*deleted_doc, deleted_doc->version()); - ASSERT_EQ(cache_->Get(testutil::Key(kDocPath)), deleted_doc); + ASSERT_EQ(cache_->Get(Key(kDocPath)), deleted_doc); }); } TEST_P(RemoteDocumentCacheTest, SetDocumentToNewValue) { persistence_->Run("test_set_document_to_new_value", [&] { SetTestDocument(kDocPath); - absl::optional new_doc = + absl::optional new_doc = Doc(kDocPath, kVersion, Map("data", 2)); cache_->Add(*new_doc, new_doc->version()); - ASSERT_EQ(cache_->Get(testutil::Key(kDocPath)), new_doc); + ASSERT_EQ(cache_->Get(Key(kDocPath)), new_doc); }); } TEST_P(RemoteDocumentCacheTest, RemoveDocument) { persistence_->Run("test_remove_document", [&] { SetTestDocument(kDocPath); - cache_->Remove(testutil::Key(kDocPath)); + cache_->Remove(Key(kDocPath)); - ASSERT_EQ(cache_->Get(testutil::Key(kDocPath)), absl::nullopt); + ASSERT_FALSE(cache_->Get(Key(kDocPath)).is_valid_document()); }); } TEST_P(RemoteDocumentCacheTest, RemoveNonExistentDocument) { persistence_->Run("test_remove_non_existent_document", [&] { // no-op, but make sure it doesn't throw. - EXPECT_NO_THROW(cache_->Remove(testutil::Key(kDocPath))); + EXPECT_NO_THROW(cache_->Remove(Key(kDocPath))); }); } @@ -199,12 +200,13 @@ TEST_P(RemoteDocumentCacheTest, DocumentsMatchingQuery) { SetTestDocument("c/1"); core::Query query = Query("b"); - DocumentMap results = cache_->GetMatching(query, SnapshotVersion::None()); - std::vector docs = { - Doc("b/1", kVersion, kDocData), - Doc("b/2", kVersion, kDocData), + MutableDocumentMap results = + cache_->GetMatching(query, SnapshotVersion::None()); + std::vector docs = { + Doc("b/1", kVersion, Map("a", 1, "b", 2)), + Doc("b/2", kVersion, Map("a", 1, "b", 2)), }; - EXPECT_THAT(results.underlying_map(), HasExactlyDocs(docs)); + EXPECT_THAT(results, HasExactlyDocs(docs)); }); } @@ -215,11 +217,11 @@ TEST_P(RemoteDocumentCacheTest, DocumentsMatchingQuerySinceReadTime) { SetTestDocument("b/new", /* updateTime= */ 3, /* readTime= = */ 13); core::Query query = Query("b"); - DocumentMap results = cache_->GetMatching(query, Version(12)); - std::vector docs = { - Doc("b/new", 3, kDocData), + MutableDocumentMap results = cache_->GetMatching(query, Version(12)); + std::vector docs = { + Doc("b/new", 3, Map("a", 1, "b", 2)), }; - EXPECT_THAT(results.underlying_map(), HasExactlyDocs(docs)); + EXPECT_THAT(results, HasExactlyDocs(docs)); }); } @@ -230,35 +232,76 @@ TEST_P(RemoteDocumentCacheTest, DocumentsMatchingUsesReadTimeNotUpdateTime) { SetTestDocument("b/new", /* updateTime= */ 2, /* readTime= */ 1); core::Query query = Query("b"); - DocumentMap results = cache_->GetMatching(query, Version(1)); - std::vector docs = { - Doc("b/old", 1, kDocData), + MutableDocumentMap results = cache_->GetMatching(query, Version(1)); + std::vector docs = { + Doc("b/old", 1, Map("a", 1, "b", 2)), }; - EXPECT_THAT(results.underlying_map(), HasExactlyDocs(docs)); + EXPECT_THAT(results, HasExactlyDocs(docs)); }); } +TEST_P(RemoteDocumentCacheTest, DoesNotApplyDocumentModificationsToCache) { + // This test verifies that the MemoryMutationCache returns copies of all + // data to ensure that the documents in the cache cannot be modified. + persistence_->Run("test_does_not_apply_document_modifications_to_cache", [&] { + MutableDocument document = SetTestDocument("coll/doc", Map("value", "old")); + document = cache_->Get(Key("coll/doc")); + EXPECT_EQ(document.value(), *Map("value", "old")); + document.data().Set(Field("value"), Value("new")); + + document = cache_->Get(Key("coll/doc")); + EXPECT_EQ(document.value(), *Map("value", "old")); + document.data().Set(Field("value"), Value("new")); + + MutableDocumentMap documents = + cache_->GetAll(DocumentKeySet{Key("coll/doc")}); + document = documents.find(Key("coll/doc"))->second; + EXPECT_EQ(document.value(), *Map("value", "old")); + document.data().Set(Field("value"), Value("new")); + + documents = cache_->GetMatching(Query("coll"), SnapshotVersion::None()); + document = documents.find(Key("coll/doc"))->second; + EXPECT_EQ(document.value(), *Map("value", "old")); + document.data().Set(Field("value"), Value("new")); + + document = cache_->Get(Key("coll/doc")); + EXPECT_EQ(document.value(), *Map("value", "old")); + }); +} // MARK: - Helpers -Document RemoteDocumentCacheTest::SetTestDocument(const absl::string_view path, - int update_time, - int read_time) { - Document doc = Doc(path, update_time, kDocData); +MutableDocument RemoteDocumentCacheTest::SetTestDocument( + absl::string_view path, + Message data, + int update_time, + int read_time) { + MutableDocument doc = Doc(path, update_time, std::move(data)); cache_->Add(doc, Version(read_time)); return doc; } -Document RemoteDocumentCacheTest::SetTestDocument( +MutableDocument RemoteDocumentCacheTest::SetTestDocument(absl::string_view path, + int update_time, + int read_time) { + return SetTestDocument(path, Map("a", 1, "b", 2), update_time, read_time); +} + +MutableDocument RemoteDocumentCacheTest::SetTestDocument( + absl::string_view path, Message data) { + return SetTestDocument(path, std::move(data), kVersion, kVersion); +} + +MutableDocument RemoteDocumentCacheTest::SetTestDocument( const absl::string_view path) { - return SetTestDocument(path, kVersion, kVersion); + return SetTestDocument(path, Map("a", 1, "b", 2), kVersion, kVersion); } void RemoteDocumentCacheTest::SetAndReadTestDocument( const absl::string_view path) { persistence_->Run("SetAndReadTestDocument", [&] { - Document written = SetTestDocument(path); - absl::optional read = cache_->Get(testutil::Key(path)); - ASSERT_EQ(*read, written); + MutableDocument written = SetTestDocument(path); + absl::optional read = cache_->Get(Key(path)); + EXPECT_EQ(*read, written); }); } diff --git a/Firestore/core/test/unit/local/remote_document_cache_test.h b/Firestore/core/test/unit/local/remote_document_cache_test.h index 5bd515342d5..096677da481 100644 --- a/Firestore/core/test/unit/local/remote_document_cache_test.h +++ b/Firestore/core/test/unit/local/remote_document_cache_test.h @@ -19,6 +19,8 @@ #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/core/src/nanopb/message.h" #include "absl/strings/string_view.h" #include "gtest/gtest.h" @@ -26,7 +28,7 @@ namespace firebase { namespace firestore { namespace model { -class Document; +class MutableDocument; } // namespace model @@ -53,10 +55,17 @@ class RemoteDocumentCacheTest : public ::testing::TestWithParam { RemoteDocumentCacheTest(); protected: - model::Document SetTestDocument(absl::string_view path); - model::Document SetTestDocument(absl::string_view path, - int update_time, - int read_time); + model::MutableDocument SetTestDocument(absl::string_view path); + model::MutableDocument SetTestDocument( + absl::string_view path, nanopb::Message data); + model::MutableDocument SetTestDocument(absl::string_view path, + int update_time, + int read_time); + model::MutableDocument SetTestDocument( + absl::string_view path, + nanopb::Message data, + int update_time, + int read_time); void SetAndReadTestDocument(absl::string_view path); std::unique_ptr persistence_; diff --git a/Firestore/core/test/unit/model/CMakeLists.txt b/Firestore/core/test/unit/model/CMakeLists.txt index b80c3b72241..72ecb5ed203 100644 --- a/Firestore/core/test/unit/model/CMakeLists.txt +++ b/Firestore/core/test/unit/model/CMakeLists.txt @@ -14,7 +14,6 @@ firebase_ios_glob( sources *.cc *.h - EXCLUDE *_benchmark.cc ) if(FIREBASE_IOS_BUILD_TESTS) diff --git a/Firestore/core/test/unit/model/document_set_test.cc b/Firestore/core/test/unit/model/document_set_test.cc index 27db45cb237..11d57f5ea88 100644 --- a/Firestore/core/test/unit/model/document_set_test.cc +++ b/Firestore/core/test/unit/model/document_set_test.cc @@ -52,17 +52,17 @@ TEST_F(DocumentSetTest, Count) { TEST_F(DocumentSetTest, HasKey) { DocumentSet set = DocSet(comp_, {doc1_, doc2_}); - EXPECT_TRUE(set.ContainsKey(doc1_.key())); - EXPECT_TRUE(set.ContainsKey(doc2_.key())); - EXPECT_FALSE(set.ContainsKey(doc3_.key())); + EXPECT_TRUE(set.ContainsKey(doc1_->key())); + EXPECT_TRUE(set.ContainsKey(doc2_->key())); + EXPECT_FALSE(set.ContainsKey(doc3_->key())); } TEST_F(DocumentSetTest, DocumentForKey) { DocumentSet set = DocSet(comp_, {doc1_, doc2_}); - EXPECT_EQ(set.GetDocument(doc1_.key()), doc1_); - EXPECT_EQ(set.GetDocument(doc2_.key()), doc2_); - EXPECT_EQ(set.GetDocument(doc3_.key()), absl::nullopt); + EXPECT_EQ(set.GetDocument(doc1_->key()), doc1_); + EXPECT_EQ(set.GetDocument(doc2_->key()), doc2_); + EXPECT_EQ(set.GetDocument(doc3_->key()), absl::nullopt); } TEST_F(DocumentSetTest, FirstAndLastDocument) { @@ -83,14 +83,14 @@ TEST_F(DocumentSetTest, KeepsDocumentsInTheRightOrder) { TEST_F(DocumentSetTest, Deletes) { DocumentSet set = DocSet(comp_, {doc1_, doc2_, doc3_}); - DocumentSet set_without_doc1 = set.erase(doc1_.key()); + DocumentSet set_without_doc1 = set.erase(doc1_->key()); ASSERT_THAT(set_without_doc1, ElementsAre(doc3_, doc2_)); EXPECT_EQ(set_without_doc1.size(), 2); // Original remains unchanged ASSERT_THAT(set, ElementsAre(doc3_, doc1_, doc2_)); - DocumentSet set_without_doc3 = set_without_doc1.erase(doc3_.key()); + DocumentSet set_without_doc3 = set_without_doc1.erase(doc3_->key()); ASSERT_THAT(set_without_doc3, ElementsAre(doc2_)); EXPECT_EQ(set_without_doc3.size(), 1); } @@ -102,7 +102,7 @@ TEST_F(DocumentSetTest, Updates) { set = set.insert(doc2_prime); ASSERT_EQ(set.size(), 3); - EXPECT_EQ(set.GetDocument(doc2_prime.key()), doc2_prime); + EXPECT_EQ(set.GetDocument(doc2_prime->key()), doc2_prime); ASSERT_THAT(set, ElementsAre(doc3_, doc1_, doc2_prime)); } diff --git a/Firestore/core/test/unit/model/document_test.cc b/Firestore/core/test/unit/model/document_test.cc index aca6665536b..7ea03d0ae0f 100644 --- a/Firestore/core/test/unit/model/document_test.cc +++ b/Firestore/core/test/unit/model/document_test.cc @@ -14,11 +14,9 @@ * limitations under the License. */ -#include "Firestore/core/src/model/document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/model/unknown_document.h" #include "Firestore/core/test/unit/testutil/testutil.h" #include "absl/strings/string_view.h" #include "gtest/gtest.h" @@ -27,10 +25,12 @@ namespace firebase { namespace firestore { namespace model { +using testutil::DeletedDoc; using testutil::Doc; using testutil::Field; using testutil::Key; using testutil::Map; +using testutil::UnknownDoc; using testutil::Value; using testutil::Version; using testutil::WrapObject; @@ -39,45 +39,48 @@ TEST(DocumentTest, Constructor) { DocumentKey key = Key("messages/first"); SnapshotVersion version = Version(1001); ObjectValue data = WrapObject("a", 1); - Document doc(data, key, version, DocumentState::kSynced); + MutableDocument doc = MutableDocument::FoundDocument(key, version, data); - EXPECT_EQ(MaybeDocument::Type::Document, doc.type()); + EXPECT_TRUE(doc.is_found_document()); EXPECT_EQ(doc.key(), Key("messages/first")); EXPECT_EQ(doc.version(), version); EXPECT_EQ(doc.data(), data); EXPECT_EQ(doc.has_local_mutations(), false); EXPECT_EQ(doc.has_pending_writes(), false); - Document doc2(data, key, version, DocumentState::kLocalMutations); + MutableDocument doc2 = + MutableDocument::FoundDocument(key, version, data).SetHasLocalMutations(); EXPECT_EQ(doc2.has_local_mutations(), true); EXPECT_EQ(doc2.has_pending_writes(), true); - Document doc3(data, key, version, DocumentState::kCommittedMutations); + MutableDocument doc3 = MutableDocument::FoundDocument(key, version, data) + .SetHasCommittedMutations(); EXPECT_EQ(doc3.has_committed_mutations(), true); EXPECT_EQ(doc3.has_pending_writes(), true); } TEST(DocumentTest, ExtractsFields) { - Document doc = Doc("rooms/eros", 1001, - Map("desc", "Discuss all the project related stuff", - "owner", Map("name", "Jonny", "title", "scallywag"))); + MutableDocument doc = + Doc("rooms/eros", 1001, + Map("desc", "Discuss all the project related stuff", "owner", + Map("name", "Jonny", "title", "scallywag"))); EXPECT_EQ(doc.field(Field("desc")), - Value("Discuss all the project related stuff")); - EXPECT_EQ(doc.field(Field("owner.title")), Value("scallywag")); + *Value("Discuss all the project related stuff")); + EXPECT_EQ(doc.field(Field("owner.title")), *Value("scallywag")); } TEST(DocumentTest, Equality) { - Document doc = Doc("some/path", 1, Map("a", 1)); + MutableDocument doc = Doc("some/path", 1, Map("a", 1)); EXPECT_EQ(doc, Doc("some/path", 1, Map("a", 1))); EXPECT_NE(doc, Doc("other/path", 1, Map("a", 1))); EXPECT_NE(doc, Doc("some/path", 2, Map("a", 1))); EXPECT_NE(doc, Doc("some/path", 1, Map("b", 1))); EXPECT_NE(doc, Doc("some/path", 1, Map("a", 2))); - EXPECT_NE(doc, - Doc("some/path", 1, Map("a", 1), DocumentState::kLocalMutations)); + EXPECT_NE(doc, Doc("some/path", 1, Map("a", 1)).SetHasLocalMutations()); - EXPECT_NE(doc, UnknownDocument(Key("same/path"), Version(1))); + EXPECT_NE(doc, UnknownDoc("same/path", 1)); + EXPECT_NE(DeletedDoc("same/path", 1), UnknownDoc("same/path", 1)); } } // namespace model diff --git a/Firestore/core/test/unit/model/field_value_benchmark.cc b/Firestore/core/test/unit/model/field_value_benchmark.cc deleted file mode 100644 index 6271f0a96ed..00000000000 --- a/Firestore/core/test/unit/model/field_value_benchmark.cc +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2019 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/model/field_value.h" - -#include -#include - -#include "Firestore/core/src/util/secure_random.h" -#include "Firestore/core/test/unit/testutil/testutil.h" -#include "absl/types/variant.h" -#include "benchmark/benchmark.h" - -namespace firebase { -namespace firestore { -namespace model { -namespace { - -using Type = FieldValue::Type; - -using testutil::Key; -using util::SecureRandom; - -std::string RandomString(SecureRandom* rnd, size_t len) { - std::string s; - std::generate_n(std::back_inserter(s), len, - [&] { return rnd->Uniform(256); }); - return s; -} - -void BM_FieldValueStringCopy(benchmark::State& state) { - util::SecureRandom rnd; - - auto len = static_cast(state.range(0)); - FieldValue str = FieldValue::FromString(RandomString(&rnd, len)); - - for (auto _ : state) { - FieldValue copy = str; - } -} -BENCHMARK(BM_FieldValueStringCopy) - ->Arg(1 << 2) - ->Arg(1 << 3) - ->Arg(1 << 4) - ->Arg(1 << 5) - ->Arg(1 << 6) - ->Arg(1 << 7) - ->Arg(1 << 8) - ->Arg(1 << 9) - ->Arg(1 << 10) - ->Arg(1 << 15); - -void BM_FieldValueStringHash(benchmark::State& state) { - util::SecureRandom rnd; - - auto len = static_cast(state.range(0)); - FieldValue str = FieldValue::FromString(RandomString(&rnd, len)); - - for (auto _ : state) { - str.Hash(); - } -} -BENCHMARK(BM_FieldValueStringHash) - ->Arg(1 << 2) - ->Arg(1 << 3) - ->Arg(1 << 4) - ->Arg(1 << 5) - ->Arg(1 << 6) - ->Arg(1 << 7) - ->Arg(1 << 8) - ->Arg(1 << 9) - ->Arg(1 << 10); - -void BM_FieldValueIntegerFill(benchmark::State& state) { - std::vector values; - for (auto _ : state) { - values.push_back(FieldValue::FromInteger(42)); - } -} -BENCHMARK(BM_FieldValueIntegerFill); - -void BM_FieldValueStringFill(benchmark::State& state) { - SecureRandom rnd; - auto len = static_cast(state.range(0)); - std::string str = RandomString(&rnd, len); - - std::vector values; - for (auto _ : state) { - values.push_back(FieldValue::FromString(str)); - } -} -BENCHMARK(BM_FieldValueStringFill) - ->Arg(1 << 2) - ->Arg(1 << 3) - ->Arg(1 << 4) - ->Arg(1 << 5) - ->Arg(1 << 6) - ->Arg(1 << 7) - ->Arg(1 << 8) - ->Arg(1 << 9) - ->Arg(1 << 10); - -using UserType = absl::variant; -struct FromValueVisitor { - FieldValue operator()(int64_t value) { - return FieldValue::FromInteger(value); - } - FieldValue operator()(double value) { - return FieldValue::FromDouble(value); - } - FieldValue operator()(std::string value) { - return FieldValue::FromString(std::move(value)); - } - FieldValue operator()(const Timestamp& value) { - return FieldValue::FromTimestamp(value); - } -}; - -void BM_FieldValueCreation(benchmark::State& state) { - const int kValues = 128; - util::SecureRandom rnd; - - std::vector input; - std::generate_n(std::back_inserter(input), kValues, [&]() -> UserType { - auto choice = rnd.Uniform(10); - if (choice < 4) { - return static_cast(42); - } else if (choice < 8) { - return RandomString(&rnd, 16); - } else if (choice < 9) { - return static_cast(9000); - } else { - return Timestamp(42, 0); - } - }); - - FromValueVisitor visitor; - - std::vector values; - int i = 0; - for (auto _ : state) { - values.push_back(absl::visit(visitor, input[i])); - - i = (i + 1) % kValues; - } -} -BENCHMARK(BM_FieldValueCreation); - -} // namespace -} // namespace model -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/test/unit/model/field_value_test.cc b/Firestore/core/test/unit/model/field_value_test.cc deleted file mode 100644 index 6cf6642d610..00000000000 --- a/Firestore/core/test/unit/model/field_value_test.cc +++ /dev/null @@ -1,776 +0,0 @@ -/* - * Copyright 2018 Google LLC - * - * 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/model/field_value.h" - -#if __APPLE__ -#import -#endif // __APPLE__ - -#include // NOLINT(build/c++11) -#include -#include -#include - -#include "Firestore/core/src/model/field_mask.h" -#include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/nanopb/byte_string.h" -#include "Firestore/core/src/util/defer.h" -#include "Firestore/core/src/util/string_apple.h" -#include "Firestore/core/test/unit/testutil/equals_tester.h" -#include "Firestore/core/test/unit/testutil/testutil.h" -#include "Firestore/core/test/unit/testutil/time_testing.h" -#include "absl/base/casts.h" -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { -namespace model { - -using Type = FieldValue::Type; - -using absl::nullopt; -using nanopb::ByteString; -using testing::Not; -using testutil::Array; -using testutil::BlobValue; -using testutil::DbId; -using testutil::Field; -using testutil::Key; -using testutil::Map; -using testutil::time_point; -using testutil::Value; -using testutil::WrapObject; - -using Sec = std::chrono::seconds; -using Ms = std::chrono::milliseconds; - -namespace { - -uint64_t ToBits(double value) { - return absl::bit_cast(value); -} - -double ToDouble(uint64_t value) { - return absl::bit_cast(value); -} - -// All permutations of the 51 other non-MSB significand bits are also NaNs. -const uint64_t kAlternateNanBits = 0x7fff000000000000ULL; - -MATCHER(IsNan, "a NaN") { - return std::isnan(arg); -} - -} // namespace - -class FieldValueTest : public testing::Test { - public: - static FieldValue FromServerTimestamp( - const Timestamp& local_write_time, - absl::optional previous_value = absl::nullopt) { - return FieldValue::FromServerTimestamp(local_write_time, previous_value); - } -}; - -TEST_F(FieldValueTest, ValueHelpers) { - // Validates that the Value helpers in testutil produce the right types - FieldValue bool_value = Value(true); - ASSERT_EQ(bool_value.type(), Type::Boolean); - EXPECT_EQ(bool_value.boolean_value(), true); - - FieldValue int_value = Value(5); - ASSERT_EQ(int_value.type(), Type::Integer); - EXPECT_EQ(int_value.integer_value(), 5); - - FieldValue long_value = Value(LONG_MAX); - ASSERT_EQ(long_value.type(), Type::Integer); - EXPECT_EQ(long_value.integer_value(), LONG_MAX); - - FieldValue long_long_value = Value(LLONG_MAX); - ASSERT_EQ(long_long_value.type(), Type::Integer); - EXPECT_EQ(long_long_value.integer_value(), LLONG_MAX); - - FieldValue double_value = Value(2.0); - ASSERT_EQ(double_value.type(), Type::Double); - EXPECT_EQ(double_value.double_value(), 2.0); -} - -TEST_F(FieldValueTest, ExtractsFields) { - ObjectValue value = WrapObject("foo", Map("a", 1, "b", true, "c", "string")); - - ASSERT_EQ(Type::Object, value.Get(Field("foo"))->type()); - - EXPECT_EQ(Value(1), value.Get(Field("foo.a"))); - EXPECT_EQ(Value(true), value.Get(Field("foo.b"))); - EXPECT_EQ(Value("string"), value.Get(Field("foo.c"))); - - EXPECT_EQ(nullopt, value.Get(Field("foo.a.b"))); - EXPECT_EQ(nullopt, value.Get(Field("bar"))); - EXPECT_EQ(nullopt, value.Get(Field("bar.a"))); -} - -TEST_F(FieldValueTest, ExtractsFieldMask) { - ObjectValue value = - WrapObject("a", "b", "map", - Map("a", 1, "b", true, "c", "string", "nested", Map("d", "e")), - "emptymap", Map()); - - FieldMask expected_mask = - FieldMask({Field("a"), Field("map.a"), Field("map.b"), Field("map.c"), - Field("map.nested.d"), Field("emptymap")}); - FieldMask actual_mask = value.ToFieldMask(); - - EXPECT_EQ(expected_mask, actual_mask); -} - -TEST_F(FieldValueTest, OverwritesExistingFields) { - ObjectValue old = WrapObject("a", "old"); - ObjectValue mod = old.Set(Field("a"), Value("mod")); - EXPECT_NE(old, mod); - EXPECT_EQ(WrapObject("a", "old"), old); - EXPECT_EQ(WrapObject("a", "mod"), mod); -} - -TEST_F(FieldValueTest, AddsNewFields) { - ObjectValue empty = ObjectValue::Empty(); - ObjectValue mod = empty.Set(Field("a"), Value("mod")); - EXPECT_EQ(ObjectValue::Empty(), empty); - EXPECT_EQ(WrapObject("a", "mod"), mod); - - ObjectValue old = mod; - mod = old.Set(Field("b"), Value(1)); - EXPECT_EQ(WrapObject("a", "mod"), old); - EXPECT_EQ(WrapObject("a", "mod", "b", 1), mod); -} - -TEST_F(FieldValueTest, ImplicitlyCreatesObjects) { - ObjectValue old = WrapObject("a", "old"); - ObjectValue mod = old.Set(Field("b.c.d"), Value("mod")); - - EXPECT_NE(old, mod); - EXPECT_EQ(WrapObject("a", "old"), old); - EXPECT_EQ(WrapObject("a", "old", "b", Map("c", Map("d", "mod"))), mod); -} - -TEST_F(FieldValueTest, CanOverwritePrimitivesWithObjects) { - ObjectValue old = WrapObject("a", Map("b", "old")); - ObjectValue mod = old.Set(Field("a"), WrapObject("b", "mod")); - EXPECT_NE(old, mod); - EXPECT_EQ(WrapObject("a", Map("b", "old")), old); - EXPECT_EQ(WrapObject("a", Map("b", "mod")), mod); -} - -TEST_F(FieldValueTest, AddsToNestedObjects) { - ObjectValue old = WrapObject("a", Map("b", "old")); - ObjectValue mod = old.Set(Field("a.c"), Value("mod")); - EXPECT_NE(old, mod); - EXPECT_EQ(WrapObject("a", Map("b", "old")), old); - EXPECT_EQ(WrapObject("a", Map("b", "old", "c", "mod")), mod); -} - -TEST_F(FieldValueTest, DeletesKey) { - ObjectValue old = WrapObject("a", 1, "b", 2); - ObjectValue mod = old.Delete(Field("a")); - - EXPECT_NE(old, mod); - EXPECT_EQ(WrapObject("a", 1, "b", 2), old); - EXPECT_EQ(WrapObject("b", 2), mod); - - ObjectValue empty = mod.Delete(Field("b")); - EXPECT_NE(mod, empty); - EXPECT_EQ(WrapObject("b", 2), mod); - EXPECT_EQ(ObjectValue::Empty(), empty); -} - -TEST_F(FieldValueTest, DeletesHandleMissingKeys) { - ObjectValue old = WrapObject("a", Map("b", 1, "c", 2)); - ObjectValue mod = old.Delete(Field("b")); - EXPECT_EQ(mod, old); - EXPECT_EQ(WrapObject("a", Map("b", 1, "c", 2)), mod); - - mod = old.Delete(Field("a.d")); - EXPECT_EQ(mod, old); - EXPECT_EQ(WrapObject("a", Map("b", 1, "c", 2)), mod); - - mod = old.Delete(Field("a.b.c")); - EXPECT_EQ(mod, old); - EXPECT_EQ(WrapObject("a", Map("b", 1, "c", 2)), mod); -} - -TEST_F(FieldValueTest, DeletesNestedKeys) { - FieldValue::Map orig = Map("a", Map("b", 1, "c", Map("d", 2, "e", 3))); - ObjectValue old = WrapObject(orig); - ObjectValue mod = old.Delete(Field("a.c.d")); - - EXPECT_NE(mod, old); - - FieldValue::Map second = Map("a", Map("b", 1, "c", Map("e", 3))); - EXPECT_EQ(WrapObject(second), mod); - - old = mod; - mod = old.Delete(Field("a.c")); - - EXPECT_NE(old, mod); - EXPECT_EQ(WrapObject(second), old); - - FieldValue::Map third = Map("a", Map("b", 1)); - EXPECT_EQ(WrapObject(third), mod); - - old = mod; - mod = old.Delete(Field("a")); - - EXPECT_NE(old, mod); - EXPECT_EQ(WrapObject(third), old); - EXPECT_EQ(ObjectValue::Empty(), mod); -} - -#if defined(_WIN32) -#define timegm _mkgmtime - -#elif defined(__ANDROID__) -// time.h doesn't necessarily define this properly, even though it's present -// from API 12 and up. -extern time_t timegm(struct tm*); -#endif - -static time_point kDate1 = testutil::MakeTimePoint(2016, 5, 20, 10, 20, 0); -static Timestamp kTimestamp1{1463739600, 0}; - -static time_point kDate2 = testutil::MakeTimePoint(2016, 10, 21, 15, 32, 0); -static Timestamp kTimestamp2{1477063920, 0}; - -TEST_F(FieldValueTest, Equality) { - // Avoid statically dividing by zero; MSVC considers this an error. - double zero = 0.0; - testutil::EqualsTester() - .AddEqualityGroup(FieldValue::Null(), Value(nullptr)) - .AddEqualityGroup(FieldValue::False(), Value(false)) - .AddEqualityGroup(FieldValue::True(), Value(true)) - .AddEqualityGroup(Value(0.0 / zero), Value(ToDouble(kCanonicalNanBits)), - Value(ToDouble(kAlternateNanBits)), - Value(std::nan("1")), Value(std::nan("2"))) - // -0.0 and 0.0 compare the same but are not equal. - .AddEqualityGroup(Value(-0.0)) - .AddEqualityGroup(Value(0.0)) - .AddEqualityGroup(Value(1), FieldValue::FromInteger(1LL)) - // Doubles and Longs aren't equal (even though they compare same). - .AddEqualityGroup(Value(1.0), FieldValue::FromDouble(1.0)) - .AddEqualityGroup(Value(1.1), FieldValue::FromDouble(1.1)) - .AddEqualityGroup(BlobValue(0, 1, 1)) - .AddEqualityGroup(BlobValue(0, 1)) - .AddEqualityGroup(Value("string"), FieldValue::FromString("string")) - .AddEqualityGroup(Value("strin")) - // latin small letter e + combining acute accent - .AddEqualityGroup(Value("e\u0301b")) - // latin small letter e with acute accent - .AddEqualityGroup(Value("\u00e9a")) - .AddEqualityGroup(Value(Timestamp::FromTimePoint(kDate1)), - Value(kTimestamp1)) - .AddEqualityGroup(Value(Timestamp::FromTimePoint(kDate2)), - Value(kTimestamp2)) - // NOTE: ServerTimestampValues can't be parsed via Value(). - .AddEqualityGroup(FieldValue::FromServerTimestamp(kTimestamp1), - FieldValue::FromServerTimestamp(kTimestamp1)) - .AddEqualityGroup(FieldValue::FromServerTimestamp(kTimestamp2)) - .AddEqualityGroup(Value(GeoPoint(0, 1)), - FieldValue::FromGeoPoint(GeoPoint(0, 1))) - .AddEqualityGroup(Value(GeoPoint(1, 0))) - .AddEqualityGroup(FieldValue::FromReference(DbId(), Key("coll/doc1")), - FieldValue::FromReference(DbId(), Key("coll/doc1"))) - .AddEqualityGroup( - FieldValue::FromReference(DbId("project/bar"), Key("coll/doc2"))) - .AddEqualityGroup( - FieldValue::FromReference(DbId("project/baz"), Key("coll/doc2"))) - .AddEqualityGroup(Array("foo", "bar"), Array("foo", "bar")) - .AddEqualityGroup(Array("foo", "bar", "baz")) - .AddEqualityGroup(Array("foo")) - .AddEqualityGroup(WrapObject("bar", 1, "foo", 2), - WrapObject("foo", 2, "bar", 1)) - .AddEqualityGroup(WrapObject("bar", 2, "foo", 1)) - .AddEqualityGroup(WrapObject("bar", 1)) - .AddEqualityGroup(WrapObject("foo", 1)) - .TestEquals(); -} - -#if __APPLE__ -// Validates that NSNumber/CFNumber normalize NaNs to the same values that -// Firestore does. This uses CoreFoundation's CFNumber instead of NSNumber just -// to keep the test in a single file. -TEST_F(FieldValueTest, CanonicalBitsAreCanonical) { - double input = ToDouble(kAlternateNanBits); - CFNumberRef number = CFNumberCreate(nullptr, kCFNumberDoubleType, &input); - util::Defer cleanup([&] { util::SafeCFRelease(number); }); - - double actual = 0.0; - CFNumberGetValue(number, kCFNumberDoubleType, &actual); - - ASSERT_EQ(kCanonicalNanBits, ToBits(actual)); -} -#endif // __APPLE__ - -TEST_F(FieldValueTest, NormalizesNaNs) { - // NOTE: With v1 query semantics, it's no longer as important that our NaN - // representation matches the backend, since all NaNs are defined to sort as - // equal, but we preserve the normalization and this test regardless for now. - - // Bedrock assumption: our canonical NaN bits are actually a NaN. - double canonical = ToDouble(kCanonicalNanBits); - double alternate = ToDouble(kAlternateNanBits); - ASSERT_THAT(canonical, IsNan()); - ASSERT_THAT(alternate, IsNan()); - ASSERT_THAT(0.0, Not(IsNan())); - - // Round trip otherwise preserves NaNs - EXPECT_EQ(kAlternateNanBits, ToBits(alternate)); - EXPECT_NE(kCanonicalNanBits, ToBits(alternate)); - - // Creating a FieldValue from a double should normalize NaNs. - auto Normalize = [](uint64_t bits) -> uint64_t { - double value = ToDouble(bits); - double normalized = FieldValue::FromDouble(value).double_value(); - return ToBits(normalized); - }; - - EXPECT_EQ(kCanonicalNanBits, Normalize(kAlternateNanBits)); - - // A NaN that's canonical except it has the sign bit set (would be negative if - // signs mattered) - EXPECT_EQ(kCanonicalNanBits, Normalize(0xfff8000000000000ULL)); - - // A signaling NaN with significand where MSB is 0, and some non-MSB bit is - // one. - EXPECT_EQ(kCanonicalNanBits, Normalize(0xfff4000000000000ULL)); -} - -TEST_F(FieldValueTest, ToString) { - EXPECT_EQ("null", FieldValue::Null().ToString()); - EXPECT_EQ("nan", FieldValue::Nan().ToString()); - EXPECT_EQ("true", FieldValue::True().ToString()); - EXPECT_EQ("false", FieldValue::False().ToString()); - - EXPECT_EQ("-1234", FieldValue::FromInteger(-1234).ToString()); - EXPECT_EQ("0", FieldValue::FromInteger(0).ToString()); - - EXPECT_EQ("-0", FieldValue::FromDouble(-0.0).ToString()); - EXPECT_EQ("0", FieldValue::FromDouble(0.0).ToString()); - EXPECT_EQ("0.5", FieldValue::FromDouble(0.5).ToString()); - EXPECT_EQ("1e+10", FieldValue::FromDouble(1.0E10).ToString()); - - EXPECT_EQ("Timestamp(seconds=12, nanoseconds=42)", - FieldValue::FromTimestamp(Timestamp(12, 42)).ToString()); - - EXPECT_EQ( - "ServerTimestamp(local_write_time=Timestamp(seconds=12, " - "nanoseconds=42))", - FieldValue::FromServerTimestamp(Timestamp(12, 42)).ToString()); - - EXPECT_EQ("", FieldValue::FromString("").ToString()); - EXPECT_EQ("foo", FieldValue::FromString("foo").ToString()); - - // Bytes escaped as hex - auto blob = FieldValue::FromBlob(ByteString("HI")); - EXPECT_EQ("HI", blob.ToString()); - - auto ref = FieldValue::FromReference(DatabaseId("p", "d"), Key("foo/bar")); - EXPECT_EQ("Reference(key=foo/bar)", ref.ToString()); - - auto geo_point = FieldValue::FromGeoPoint(GeoPoint(41.8781, -87.6298)); - EXPECT_EQ("GeoPoint(latitude=41.8781, longitude=-87.6298)", - geo_point.ToString()); - - auto array = - FieldValue::FromArray({FieldValue::Null(), FieldValue::FromString("foo"), - FieldValue::FromInteger(42)}); - EXPECT_EQ("[null, foo, 42]", array.ToString()); - - auto object = FieldValue::FromMap({{"key1", FieldValue::FromString("value")}, - {"key2", FieldValue::FromInteger(42)}}); - EXPECT_EQ("{key1: value, key2: 42}", object.ToString()); -} - -TEST_F(FieldValueTest, NullType) { - const FieldValue value = FieldValue::Null(); - EXPECT_EQ(Type::Null, value.type()); - EXPECT_FALSE(value < value); -} - -TEST_F(FieldValueTest, BooleanType) { - const FieldValue true_value = FieldValue::FromBoolean(true); - const FieldValue false_value = FieldValue::FromBoolean(false); - EXPECT_EQ(Type::Boolean, true_value.type()); - EXPECT_FALSE(true_value < true_value); - EXPECT_FALSE(true_value < false_value); - EXPECT_FALSE(false_value < false_value); - EXPECT_TRUE(false_value < true_value); -} - -TEST_F(FieldValueTest, NumberType) { - const FieldValue nan_value = FieldValue::Nan(); - const FieldValue integer_value = FieldValue::FromInteger(10L); - const FieldValue double_value = FieldValue::FromDouble(10.1); - EXPECT_EQ(Type::Double, nan_value.type()); - EXPECT_EQ(Type::Integer, integer_value.type()); - EXPECT_EQ(Type::Double, double_value.type()); - EXPECT_TRUE(nan_value < integer_value); - EXPECT_TRUE(nan_value < double_value); - EXPECT_FALSE(nan_value < nan_value); - EXPECT_FALSE(integer_value < nan_value); - EXPECT_FALSE(integer_value < nan_value); - EXPECT_TRUE(integer_value < double_value); // 10 < 10.1 - EXPECT_FALSE(double_value < integer_value); - EXPECT_FALSE(integer_value < integer_value); - EXPECT_FALSE(double_value < double_value); - - // Number comparison craziness - // Integers - EXPECT_TRUE(FieldValue::FromInteger(1L) < FieldValue::FromInteger(2L)); - EXPECT_FALSE(FieldValue::FromInteger(1L) < FieldValue::FromInteger(1L)); - EXPECT_FALSE(FieldValue::FromInteger(2L) < FieldValue::FromInteger(1L)); - // Doubles - EXPECT_TRUE(FieldValue::FromDouble(1.0) < FieldValue::FromDouble(2.0)); - EXPECT_FALSE(FieldValue::FromDouble(1.0) < FieldValue::FromDouble(1.0)); - EXPECT_FALSE(FieldValue::FromDouble(2.0) < FieldValue::FromDouble(1.0)); - EXPECT_TRUE(FieldValue::Nan() < FieldValue::FromDouble(1.0)); - EXPECT_FALSE(FieldValue::Nan() < FieldValue::Nan()); - EXPECT_FALSE(FieldValue::FromDouble(1.0) < FieldValue::Nan()); - // Mixed - EXPECT_TRUE(FieldValue::FromDouble(-1e20) < - FieldValue::FromInteger(LLONG_MIN)); - EXPECT_FALSE(FieldValue::FromDouble(1e20) < - FieldValue::FromInteger(LLONG_MAX)); - EXPECT_TRUE(FieldValue::FromDouble(1.234) < FieldValue::FromInteger(2L)); - EXPECT_FALSE(FieldValue::FromDouble(2.345) < FieldValue::FromInteger(1L)); - EXPECT_FALSE(FieldValue::FromDouble(1.0) < FieldValue::FromInteger(1L)); - EXPECT_FALSE(FieldValue::FromDouble(1.234) < FieldValue::FromInteger(1L)); - EXPECT_FALSE(FieldValue::FromInteger(LLONG_MIN) < - FieldValue::FromDouble(-1e20)); - EXPECT_TRUE(FieldValue::FromInteger(LLONG_MAX) < - FieldValue::FromDouble(1e20)); - EXPECT_FALSE(FieldValue::FromInteger(1) < FieldValue::FromDouble(1.0)); - EXPECT_TRUE(FieldValue::FromInteger(1) < FieldValue::FromDouble(1.234)); -} - -TEST_F(FieldValueTest, TimestampType) { - const FieldValue o = FieldValue::FromTimestamp(Timestamp()); - const FieldValue a = FieldValue::FromTimestamp({100, 0}); - const FieldValue b = FieldValue::FromTimestamp({200, 0}); - EXPECT_EQ(Type::Timestamp, a.type()); - EXPECT_TRUE(o < a); - EXPECT_TRUE(a < b); - EXPECT_FALSE(a < a); - const FieldValue c = FromServerTimestamp({100, 0}); - const FieldValue d = - FromServerTimestamp({200, 0}, FieldValue::FromTimestamp({300, 0})); - EXPECT_EQ(Type::ServerTimestamp, c.type()); - EXPECT_EQ(Type::ServerTimestamp, d.type()); - EXPECT_TRUE(c < d); - EXPECT_FALSE(c < c); - // Mixed - EXPECT_TRUE(o < c); - EXPECT_TRUE(a < c); - EXPECT_TRUE(b < c); - EXPECT_TRUE(b < d); - EXPECT_FALSE(c < o); - EXPECT_FALSE(c < a); - EXPECT_FALSE(c < b); - EXPECT_FALSE(d < b); -} - -TEST_F(FieldValueTest, StringType) { - const FieldValue a = FieldValue::FromString("abc"); - std::string xyz("xyz"); - const FieldValue b = FieldValue::FromString(xyz); - const FieldValue c = FieldValue::FromString(std::move(xyz)); - EXPECT_EQ(Type::String, a.type()); - EXPECT_EQ(Type::String, b.type()); - EXPECT_EQ(Type::String, c.type()); - EXPECT_TRUE(a < b); - EXPECT_FALSE(a < a); -} - -TEST_F(FieldValueTest, BlobType) { - const FieldValue a = FieldValue::FromBlob(ByteString("abc")); - const FieldValue b = FieldValue::FromBlob(ByteString("def")); - EXPECT_EQ(Type::Blob, a.type()); - EXPECT_EQ(Type::Blob, b.type()); - EXPECT_TRUE(a < b); - EXPECT_FALSE(a < a); -} - -TEST_F(FieldValueTest, ReferenceType) { - DatabaseId id("project", "database"); - FieldValue a = FieldValue::FromReference(id, Key("root/abc")); - DocumentKey key = Key("root/def"); - FieldValue b = FieldValue::FromReference(id, key); - FieldValue c = FieldValue::FromReference(id, std::move(key)); - EXPECT_EQ(Type::Reference, a.type()); - EXPECT_EQ(Type::Reference, b.type()); - EXPECT_EQ(Type::Reference, c.type()); - EXPECT_TRUE(a < b); - EXPECT_FALSE(a < a); -} - -TEST_F(FieldValueTest, GeoPointType) { - const FieldValue a = FieldValue::FromGeoPoint({1, 2}); - const FieldValue b = FieldValue::FromGeoPoint({3, 4}); - EXPECT_EQ(Type::GeoPoint, a.type()); - EXPECT_EQ(Type::GeoPoint, b.type()); - EXPECT_TRUE(a < b); - EXPECT_FALSE(a < a); -} - -TEST_F(FieldValueTest, ArrayType) { - const FieldValue empty = FieldValue::FromArray(std::vector{}); - std::vector array{FieldValue::Null(), - FieldValue::FromBoolean(true), - FieldValue::FromBoolean(false)}; - // copy the array - const FieldValue small = FieldValue::FromArray(array); - std::vector another_array{FieldValue::FromBoolean(true), - FieldValue::FromBoolean(false)}; - // move the array - const FieldValue large = FieldValue::FromArray(std::move(another_array)); - EXPECT_EQ(Type::Array, empty.type()); - EXPECT_EQ(Type::Array, small.type()); - EXPECT_EQ(Type::Array, large.type()); - EXPECT_TRUE(empty < small); - EXPECT_FALSE(small < empty); - EXPECT_FALSE(small < small); - EXPECT_TRUE(small < large); - EXPECT_FALSE(large < small); -} - -TEST_F(FieldValueTest, ObjectType) { - const ObjectValue empty = ObjectValue::Empty(); - FieldValue::Map object{{"null", FieldValue::Null()}, - {"true", FieldValue::True()}, - {"false", FieldValue::False()}}; - // copy the map - const ObjectValue small = ObjectValue::FromMap(object); - FieldValue::Map another_object{{"null", FieldValue::Null()}, - {"true", FieldValue::False()}}; - // move the array - const ObjectValue large = ObjectValue::FromMap(std::move(another_object)); - EXPECT_TRUE(empty < small); - EXPECT_FALSE(small < empty); - EXPECT_FALSE(small < small); - EXPECT_TRUE(small < large); - EXPECT_FALSE(large < small); -} - -TEST_F(FieldValueTest, Copy) { - FieldValue clone = FieldValue::True(); - const FieldValue null_value = FieldValue::Null(); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); - EXPECT_EQ(FieldValue::Null(), null_value); - clone = *&clone; - EXPECT_EQ(FieldValue::Null(), clone); - - const FieldValue true_value = FieldValue::True(); - clone = true_value; - EXPECT_EQ(FieldValue::True(), clone); - EXPECT_EQ(FieldValue::True(), true_value); - clone = *&clone; - EXPECT_EQ(FieldValue::True(), clone); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); - - const FieldValue nan_value = FieldValue::Nan(); - clone = nan_value; - EXPECT_EQ(FieldValue::Nan(), clone); - EXPECT_EQ(FieldValue::Nan(), nan_value); - clone = *&clone; - EXPECT_EQ(FieldValue::Nan(), clone); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); - - const FieldValue integer_value = FieldValue::FromInteger(1L); - clone = integer_value; - EXPECT_EQ(FieldValue::FromInteger(1L), clone); - EXPECT_EQ(FieldValue::FromInteger(1L), integer_value); - clone = *&clone; - EXPECT_EQ(FieldValue::FromInteger(1L), clone); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); - - const FieldValue double_value = FieldValue::FromDouble(1.0); - clone = double_value; - EXPECT_EQ(FieldValue::FromDouble(1.0), clone); - EXPECT_EQ(FieldValue::FromDouble(1.0), double_value); - clone = *&clone; - EXPECT_EQ(FieldValue::FromDouble(1.0), clone); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); - - const FieldValue timestamp_value = FieldValue::FromTimestamp({100, 200}); - clone = timestamp_value; - EXPECT_EQ(FieldValue::FromTimestamp({100, 200}), clone); - EXPECT_EQ(FieldValue::FromTimestamp({100, 200}), timestamp_value); - clone = *&clone; - EXPECT_EQ(FieldValue::FromTimestamp({100, 200}), clone); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); - - const FieldValue server_timestamp_value = - FromServerTimestamp({1, 2}, FieldValue::FromTimestamp({3, 4})); - clone = server_timestamp_value; - EXPECT_EQ(FromServerTimestamp({1, 2}, FieldValue::FromTimestamp({3, 4})), - clone); - EXPECT_EQ(FromServerTimestamp({1, 2}, FieldValue::FromTimestamp({3, 4})), - server_timestamp_value); - clone = *&clone; - EXPECT_EQ(FromServerTimestamp({1, 2}, FieldValue::FromTimestamp({3, 4})), - clone); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); - - const FieldValue string_value = FieldValue::FromString("abc"); - clone = string_value; - EXPECT_EQ(FieldValue::FromString("abc"), clone); - EXPECT_EQ(FieldValue::FromString("abc"), string_value); - clone = *&clone; - EXPECT_EQ(FieldValue::FromString("abc"), clone); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); - - const FieldValue blob_value = FieldValue::FromBlob(ByteString("abc")); - clone = blob_value; - EXPECT_EQ(FieldValue::FromBlob(ByteString("abc")), clone); - EXPECT_EQ(FieldValue::FromBlob(ByteString("abc")), blob_value); - clone = *&clone; - EXPECT_EQ(FieldValue::FromBlob(ByteString("abc")), clone); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); - - DatabaseId database_id("project", "database"); - FieldValue reference_value = - FieldValue::FromReference(database_id, Key("root/abc")); - clone = reference_value; - EXPECT_EQ(FieldValue::FromReference(database_id, Key("root/abc")), clone); - EXPECT_EQ(FieldValue::FromReference(database_id, Key("root/abc")), - reference_value); - clone = *&clone; - EXPECT_EQ(FieldValue::FromReference(database_id, Key("root/abc")), clone); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); - - const FieldValue geo_point_value = FieldValue::FromGeoPoint({1, 2}); - clone = geo_point_value; - EXPECT_EQ(FieldValue::FromGeoPoint({1, 2}), clone); - EXPECT_EQ(FieldValue::FromGeoPoint({1, 2}), geo_point_value); - clone = *&clone; - EXPECT_EQ(FieldValue::FromGeoPoint({1, 2}), clone); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); - - const FieldValue array_value = FieldValue::FromArray( - std::vector{FieldValue::True(), FieldValue::False()}); - clone = array_value; - EXPECT_EQ(FieldValue::FromArray(std::vector{FieldValue::True(), - FieldValue::False()}), - clone); - EXPECT_EQ(FieldValue::FromArray(std::vector{FieldValue::True(), - FieldValue::False()}), - array_value); - clone = *&clone; - EXPECT_EQ(FieldValue::FromArray(std::vector{FieldValue::True(), - FieldValue::False()}), - clone); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); - - const FieldValue object_value = FieldValue::FromMap( - {{"true", FieldValue::True()}, {"false", FieldValue::False()}}); - clone = object_value; - EXPECT_EQ(FieldValue::FromMap( - {{"true", FieldValue::True()}, {"false", FieldValue::False()}}), - clone); - EXPECT_EQ(FieldValue::FromMap( - {{"true", FieldValue::True()}, {"false", FieldValue::False()}}), - object_value); - clone = *&clone; - EXPECT_EQ(FieldValue::FromMap( - {{"true", FieldValue::True()}, {"false", FieldValue::False()}}), - clone); - clone = null_value; - EXPECT_EQ(FieldValue::Null(), clone); -} - -TEST_F(FieldValueTest, CompareMixedType) { - const FieldValue null_value = FieldValue::Null(); - const FieldValue true_value = FieldValue::True(); - const FieldValue number_value = FieldValue::Nan(); - const FieldValue timestamp_value = FieldValue::FromTimestamp({100, 200}); - const FieldValue string_value = FieldValue::FromString("abc"); - const FieldValue blob_value = FieldValue::FromBlob(ByteString("abc")); - const DatabaseId database_id("project", "database"); - const FieldValue reference_value = - FieldValue::FromReference(database_id, Key("root/abc")); - const FieldValue geo_point_value = FieldValue::FromGeoPoint({1, 2}); - const FieldValue array_value = - FieldValue::FromArray(std::vector()); - const FieldValue object_value = FieldValue::EmptyObject(); - EXPECT_TRUE(null_value < true_value); - EXPECT_TRUE(true_value < number_value); - EXPECT_TRUE(number_value < timestamp_value); - EXPECT_TRUE(timestamp_value < string_value); - EXPECT_TRUE(string_value < blob_value); - EXPECT_TRUE(blob_value < reference_value); - EXPECT_TRUE(reference_value < geo_point_value); - EXPECT_TRUE(geo_point_value < array_value); - EXPECT_TRUE(array_value < object_value); -} - -TEST_F(FieldValueTest, CompareWithOperator) { - const FieldValue small = FieldValue::Null(); - const FieldValue large = FieldValue::True(); - - EXPECT_TRUE(small < large); - EXPECT_FALSE(small < small); - EXPECT_FALSE(large < small); - - EXPECT_TRUE(large > small); - EXPECT_FALSE(small > small); - EXPECT_FALSE(small > large); - - EXPECT_TRUE(large >= small); - EXPECT_TRUE(small >= small); - EXPECT_FALSE(small >= large); - - EXPECT_TRUE(small <= large); - EXPECT_TRUE(small <= small); - EXPECT_FALSE(large <= small); - - EXPECT_TRUE(small != large); - EXPECT_FALSE(small != small); - - EXPECT_TRUE(small == small); - EXPECT_FALSE(small == large); -} - -TEST_F(FieldValueTest, IsSmallish) { - // We expect the FV to use 4 bytes to track the type of the union, plus 8 - // bytes for the union contents themselves. The other 4 is for padding. We - // want to keep FV as small as possible. - EXPECT_LE(sizeof(FieldValue), 2 * sizeof(int64_t)); -} - -} // namespace model -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/test/unit/model/mutation_test.cc b/Firestore/core/test/unit/model/mutation_test.cc index 73aef0730fd..e34fba81404 100644 --- a/Firestore/core/test/unit/model/mutation_test.cc +++ b/Firestore/core/test/unit/model/mutation_test.cc @@ -19,13 +19,12 @@ #include #include "Firestore/core/src/model/delete_mutation.h" -#include "Firestore/core/src/model/document.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/model/maybe_document.h" -#include "Firestore/core/src/model/no_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/patch_mutation.h" +#include "Firestore/core/src/model/server_timestamp_util.h" #include "Firestore/core/src/model/set_mutation.h" #include "Firestore/core/src/model/transform_operation.h" +#include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/test/unit/testutil/testutil.h" #include "gtest/gtest.h" @@ -34,11 +33,13 @@ namespace firestore { namespace model { namespace { +using nanopb::Message; using testutil::Array; using testutil::DeletedDoc; using testutil::DeleteMutation; using testutil::Doc; using testutil::Field; +using testutil::Key; using testutil::Map; using testutil::MergeMutation; using testutil::MutationResult; @@ -51,110 +52,112 @@ using testutil::WrapObject; const Timestamp now = Timestamp::Now(); TEST(MutationTest, AppliesSetsToDocuments) { - Document base_doc = + MutableDocument doc = Doc("collection/key", 0, Map("foo", "foo-value", "baz", "baz-value")); Mutation set = SetMutation("collection/key", Map("bar", "bar-value")); - auto result = set.ApplyToLocalView(base_doc, now); + set.ApplyToLocalView(doc, now); - EXPECT_EQ(result, Doc("collection/key", 0, Map("bar", "bar-value"), - DocumentState::kLocalMutations)); + EXPECT_EQ( + doc, + Doc("collection/key", 0, Map("bar", "bar-value")).SetHasLocalMutations()); } TEST(MutationTest, AppliesPatchToDocuments) { - Document base_doc = + MutableDocument doc = Doc("collection/key", 0, Map("foo", Map("bar", "bar-value"), "baz", "baz-value")); Mutation patch = PatchMutation("collection/key", Map("foo.bar", "new-bar-value")); - auto result = patch.ApplyToLocalView(base_doc, now); + patch.ApplyToLocalView(doc, now); - EXPECT_EQ(result, + EXPECT_EQ(doc, Doc("collection/key", 0, - Map("foo", Map("bar", "new-bar-value"), "baz", "baz-value"), - DocumentState::kLocalMutations)); + Map("foo", Map("bar", "new-bar-value"), "baz", "baz-value")) + .SetHasLocalMutations()); } TEST(MutationTest, AppliesPatchWithMergeToNoDocuments) { - NoDocument base_doc = DeletedDoc("collection/key", 0); + MutableDocument doc = DeletedDoc("collection/key", 0); Mutation upsert = MergeMutation( "collection/key", Map("foo.bar", "new-bar-value"), {Field("foo.bar")}); - auto result = upsert.ApplyToLocalView(base_doc, now); + upsert.ApplyToLocalView(doc, now); - EXPECT_EQ(result, - Doc("collection/key", 0, Map("foo", Map("bar", "new-bar-value")), - DocumentState::kLocalMutations)); + EXPECT_EQ(doc, + Doc("collection/key", 0, Map("foo", Map("bar", "new-bar-value"))) + .SetHasLocalMutations()); } TEST(MutationTest, AppliesPatchWithMergeToNullDocuments) { - absl::optional base_doc; + MutableDocument doc = MutableDocument::InvalidDocument(Key("collection/key")); Mutation upsert = MergeMutation( "collection/key", Map("foo.bar", "new-bar-value"), {Field("foo.bar")}); - auto result = upsert.ApplyToLocalView(base_doc, now); + upsert.ApplyToLocalView(doc, now); - EXPECT_EQ(result, - Doc("collection/key", 0, Map("foo", Map("bar", "new-bar-value")), - DocumentState::kLocalMutations)); + EXPECT_EQ(doc, + Doc("collection/key", 0, Map("foo", Map("bar", "new-bar-value"))) + .SetHasLocalMutations()); } TEST(MutationTest, DeletesValuesFromTheFieldMask) { - Document base_doc = + MutableDocument doc = Doc("collection/key", 0, Map("foo", Map("bar", "bar-value", "baz", "baz-value"))); Mutation patch = MergeMutation("collection/key", Map(), {Field("foo.bar")}); - auto result = patch.ApplyToLocalView(base_doc, now); + patch.ApplyToLocalView(doc, now); - EXPECT_EQ(result, - Doc("collection/key", 0, Map("foo", Map("baz", "baz-value")), - DocumentState::kLocalMutations)); + EXPECT_EQ(doc, Doc("collection/key", 0, Map("foo", Map("baz", "baz-value"))) + .SetHasLocalMutations()); } TEST(MutationTest, PatchesPrimitiveValue) { - Document base_doc = + MutableDocument doc = Doc("collection/key", 0, Map("foo", "foo-value", "baz", "baz-value")); Mutation patch = PatchMutation("collection/key", Map("foo.bar", "new-bar-value")); - auto result = patch.ApplyToLocalView(base_doc, now); + patch.ApplyToLocalView(doc, now); - EXPECT_EQ(result, + EXPECT_EQ(doc, Doc("collection/key", 0, - Map("foo", Map("bar", "new-bar-value"), "baz", "baz-value"), - DocumentState::kLocalMutations)); + Map("foo", Map("bar", "new-bar-value"), "baz", "baz-value")) + .SetHasLocalMutations()); } TEST(MutationTest, PatchingDeletedDocumentsDoesNothing) { - NoDocument base_doc = testutil::DeletedDoc("collection/key", 0); + MutableDocument doc = testutil::DeletedDoc("collection/key", 0); Mutation patch = PatchMutation("collection/key", Map("foo", "bar")); - auto result = patch.ApplyToLocalView(base_doc, now); + patch.ApplyToLocalView(doc, now); - EXPECT_EQ(result, base_doc); + EXPECT_EQ(doc, testutil::DeletedDoc("collection/key", 0)); } TEST(MutationTest, AppliesLocalServerTimestampTransformToDocuments) { - Document base_doc = + MutableDocument doc = Doc("collection/key", 0, Map("foo", Map("bar", "bar-value"), "baz", "baz-value")); Mutation transform = PatchMutation("collection/key", Map(), {{"foo.bar", ServerTimestampTransform()}}); - auto result = transform.ApplyToLocalView(base_doc, now); + transform.ApplyToLocalView(doc, now); // Server timestamps aren't parsed, so we manually insert it. ObjectValue expected_data = WrapObject("foo", Map("bar", ""), "baz", "baz-value"); - expected_data = - expected_data.Set(Field("foo.bar"), FieldValue::FromServerTimestamp(now)); + expected_data.Set(Field("foo.bar"), + EncodeServerTimestamp(now, absl::nullopt)); - Document expected_doc = - Doc("collection/key", 0, expected_data, DocumentState::kLocalMutations); + MutableDocument expected_doc = + MutableDocument::FoundDocument(Key("collection/key"), Version(0), + std::move(expected_data)) + .SetHasLocalMutations(); - EXPECT_EQ(result, expected_doc); + EXPECT_EQ(doc, expected_doc); } namespace { @@ -171,21 +174,19 @@ using TransformPairs = std::vector>; * transformation is used as the input to the next. The result of applying all * transformations is then compared to the given `expected_data`. */ -void TransformBaseDoc(const FieldValue::Map& base_data, +void TransformBaseDoc(Message base_data, const TransformPairs& transforms, - const FieldValue::Map& expected_data) { - Document current_doc = Doc("collection/key", 0, base_data); + Message expected_data) { + MutableDocument current_doc = Doc("collection/key", 0, std::move(base_data)); for (const auto& transform : transforms) { Mutation mutation = PatchMutation("collection/key", Map(), {transform}); - auto result = mutation.ApplyToLocalView(current_doc, now); - ASSERT_NE(result, absl::nullopt); - ASSERT_EQ(result->type(), MaybeDocument::Type::Document); - current_doc = Document(*result); + mutation.ApplyToLocalView(current_doc, now); + EXPECT_TRUE(current_doc.is_found_document()); } - Document expected_doc = - Doc("collection/key", 0, expected_data, DocumentState::kLocalMutations); + MutableDocument expected_doc = + Doc("collection/key", 0, std::move(expected_data)).SetHasLocalMutations(); EXPECT_EQ(current_doc, expected_doc); } @@ -205,25 +206,14 @@ auto Increment(T value) -> decltype(NumericIncrementTransform(Value(value))) { template TransformOperation ArrayUnion(Args... args) { - std::vector values = {Value(args)...}; return ArrayTransform(TransformOperation::Type::ArrayUnion, - std::move(values)); + Array(std::move(args)...)); } template TransformOperation ArrayRemove(Args... args) { - std::vector values = {Value(args)...}; return ArrayTransform(TransformOperation::Type::ArrayRemove, - std::move(values)); -} - -/** - * Converts the input arguments to a vector of FieldValues wrapping the input - * types. - */ -template -static std::vector FieldValueVector(Args... values) { - return Array(values...).array_value(); + Array(std::move(args)...)); } } // namespace @@ -247,7 +237,7 @@ TEST(MutationTest, AppliesIncrementTransformToDocument) { "long_plus_long", 2L, "long_plus_double", 4.2, "double_plus_long", 6.3, "double_plus_double", 8.4, "long_plus_nan", NAN, "double_plus_nan", NAN, "long_plus_infinity", INFINITY, "double_plus_infinity", INFINITY); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesIncrementTransformToUnexpectedType) { @@ -256,7 +246,7 @@ TEST(MutationTest, AppliesIncrementTransformToUnexpectedType) { {"string", Increment(1)}, }; auto expected = Map("string", 1); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesIncrementTransformToMissingField) { @@ -265,7 +255,7 @@ TEST(MutationTest, AppliesIncrementTransformToMissingField) { {"missing", Increment(1)}, }; auto expected = Map("missing", 1); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesIncrementTransformsConsecutively) { @@ -276,7 +266,7 @@ TEST(MutationTest, AppliesIncrementTransformsConsecutively) { {"number", Increment(4)}, }; auto expected = Map("number", 10); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesIncrementWithoutOverflow) { @@ -290,7 +280,7 @@ TEST(MutationTest, AppliesIncrementWithoutOverflow) { }; auto expected = Map("a", LONG_MAX, "b", LONG_MAX, "c", LONG_MAX, "d", LONG_MAX); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesIncrementWithoutUnderflow) { @@ -304,35 +294,35 @@ TEST(MutationTest, AppliesIncrementWithoutUnderflow) { }; auto expected = Map("a", LONG_MIN, "b", LONG_MIN, "c", LONG_MIN, "d", LONG_MIN); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesLocalArrayUnionTransformToMissingField) { auto base_data = Map(); TransformPairs transforms = {{"missing", ArrayUnion(1, 2)}}; auto expected = Map("missing", Array(1, 2)); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesLocalArrayUnionTransformToNonArrayField) { auto base_data = Map("non-array", 42); TransformPairs transforms = {{"non-array", ArrayUnion(1, 2)}}; auto expected = Map("non-array", Array(1, 2)); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesLocalArrayUnionTransformWithNonExistingElements) { auto base_data = Map("array", Array(1, 3)); TransformPairs transforms = {{"array", ArrayUnion(2, 4)}}; auto expected = Map("array", Array(1, 3, 2, 4)); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesLocalArrayUnionTransformWithExistingElements) { auto base_data = Map("array", Array(1, 3)); TransformPairs transforms = {{"array", ArrayUnion(1, 3)}}; auto expected = Map("array", Array(1, 3)); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, @@ -341,7 +331,15 @@ TEST(MutationTest, auto base_data = Map("array", Array(1, 2, 2, 3)); TransformPairs transforms = {{"array", ArrayUnion(2)}}; auto expected = Map("array", Array(1, 2, 2, 3)); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); +} + +TEST(MutationTest, AppliesLocalArrayUnionTransformWithExistingElementsInOrder) { + // New elements should be appended in order. + auto base_data = Map("array", Array(1, 3)); + TransformPairs transforms = {{"array", ArrayUnion(1, 2, 3, 4, 5)}}; + auto expected = Map("array", Array(1, 3, 2, 4, 5)); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesLocalArrayUnionTransformWithDuplicateUnionElements) { @@ -349,16 +347,16 @@ TEST(MutationTest, AppliesLocalArrayUnionTransformWithDuplicateUnionElements) { auto base_data = Map("array", Array(1, 3)); TransformPairs transforms = {{"array", ArrayUnion(2, 2)}}; auto expected = Map("array", Array(1, 3, 2)); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesLocalArrayUnionTransformWithNonPrimitiveElements) { // Union nested object values (one existing, one not). auto base_data = Map("array", Array(1, Map("a", "b"))); TransformPairs transforms = { - {"array", ArrayUnion(WrapObject("a", "b"), WrapObject("c", "d"))}}; + {"array", ArrayUnion(Map("a", "b"), Map("c", "d"))}}; auto expected = Map("array", Array(1, Map("a", "b"), Map("c", "d"))); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, @@ -366,86 +364,84 @@ TEST(MutationTest, // Union objects that partially overlap an existing object. auto base_data = Map("array", Array(1, Map("a", "b", "c", "d"))); TransformPairs transforms = { - {"array", ArrayUnion(WrapObject("a", "b"), WrapObject("c", "d"))}}; + {"array", ArrayUnion(Map("a", "b"), Map("c", "d"))}}; auto expected = Map( "array", Array(1, Map("a", "b", "c", "d"), Map("a", "b"), Map("c", "d"))); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesLocalArrayRemoveTransformToMissingField) { auto base_data = Map(); TransformPairs transforms = {{"missing", ArrayRemove(1, 2)}}; auto expected = Map("missing", Array()); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesLocalArrayRemoveTransformToNonArrayField) { auto base_data = Map("non-array", 42); TransformPairs transforms = {{"non-array", ArrayRemove(1, 2)}}; auto expected = Map("non-array", Array()); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesLocalArrayRemoveTransformWithNonExistingElements) { auto base_data = Map("array", Array(1, 3)); TransformPairs transforms = {{"array", ArrayRemove(2, 4)}}; auto expected = Map("array", Array(1, 3)); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesLocalArrayRemoveTransformWithExistingElements) { auto base_data = Map("array", Array(1, 2, 3, 4)); TransformPairs transforms = {{"array", ArrayRemove(1, 3)}}; auto expected = Map("array", Array(2, 4)); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesLocalArrayRemoveTransformWithNonPrimitiveElements) { // Remove nested object values (one existing, one not). auto base_data = Map("array", Array(1, Map("a", "b"))); TransformPairs transforms = { - {"array", ArrayRemove(WrapObject("a", "b"), WrapObject("c", "d"))}}; + {"array", ArrayRemove(Map("a", "b"), Map("c", "d"))}}; auto expected = Map("array", Array(1)); - TransformBaseDoc(base_data, transforms, expected); + TransformBaseDoc(std::move(base_data), transforms, std::move(expected)); } TEST(MutationTest, AppliesServerAckedIncrementTransformToDocuments) { - Document base_doc = Doc("collection/key", 0, Map("sum", 1)); + MutableDocument doc = Doc("collection/key", 0, Map("sum", 1)); Mutation transform = SetMutation("collection/key", Map(), {{"sum", Increment(2)}}); - model::MutationResult mutation_result(Version(1), FieldValueVector(3)); + model::MutationResult mutation_result(Version(1), Array(3)); - MaybeDocument result = - transform.ApplyToRemoteDocument(base_doc, mutation_result); + transform.ApplyToRemoteDocument(doc, std::move(mutation_result)); - EXPECT_EQ(result, Doc("collection/key", 1, Map("sum", 3), - DocumentState::kCommittedMutations)); + EXPECT_EQ(doc, + Doc("collection/key", 1, Map("sum", 3)).SetHasCommittedMutations()); } TEST(MutationTest, AppliesServerAckedServerTimestampTransformToDocuments) { - Document base_doc = + MutableDocument doc = Doc("collection/key", 0, Map("foo", Map("bar", "bar-value"), "baz", "baz-value")); Mutation transform = PatchMutation("collection/key", Map(), {{"foo.bar", ServerTimestampTransform()}}); - model::MutationResult mutation_result(Version(1), FieldValueVector(now)); + model::MutationResult mutation_result(Version(1), Array(now)); - MaybeDocument result = - transform.ApplyToRemoteDocument(base_doc, mutation_result); + transform.ApplyToRemoteDocument(doc, std::move(mutation_result)); - Document expected_doc = - Doc("collection/key", 1, Map("foo", Map("bar", now), "baz", "baz-value"), - DocumentState::kCommittedMutations); + MutableDocument expected_doc = + Doc("collection/key", 1, Map("foo", Map("bar", now), "baz", "baz-value")) + .SetHasCommittedMutations(); - EXPECT_EQ(result, expected_doc); + EXPECT_EQ(doc, expected_doc); } TEST(MutationTest, AppliesServerAckedArrayTransformsToDocuments) { - Document base_doc = + MutableDocument doc = Doc("collection/key", 0, Map("array_1", Array(1, 2), "array_2", Array("a", "b"))); @@ -456,45 +452,42 @@ TEST(MutationTest, AppliesServerAckedArrayTransformsToDocuments) { }); // Server just sends null transform results for array operations. - model::MutationResult mutation_result(Version(1), - FieldValueVector(nullptr, nullptr)); + model::MutationResult mutation_result(Version(1), Array(nullptr, nullptr)); - MaybeDocument result = - transform.ApplyToRemoteDocument(base_doc, mutation_result); + transform.ApplyToRemoteDocument(doc, std::move(mutation_result)); - EXPECT_EQ(result, Doc("collection/key", 1, - Map("array_1", Array(1, 2, 3), "array_2", Array("b")), - DocumentState::kCommittedMutations)); + EXPECT_EQ(doc, Doc("collection/key", 1, + Map("array_1", Array(1, 2, 3), "array_2", Array("b"))) + .SetHasCommittedMutations()); } TEST(MutationTest, DeleteDeletes) { - Document base_doc = Doc("collection/key", 0, Map("foo", "bar")); + MutableDocument doc = Doc("collection/key", 0, Map("foo", "bar")); Mutation del = DeleteMutation("collection/key"); - auto result = del.ApplyToLocalView(base_doc, now); + del.ApplyToLocalView(doc, now); - EXPECT_EQ(result, DeletedDoc("collection/key", 0)); + EXPECT_EQ(doc, DeletedDoc("collection/key", 0)); } TEST(MutationTest, SetWithMutationResult) { - Document base_doc = Doc("collection/key", 0, Map("foo", "bar")); + MutableDocument doc = Doc("collection/key", 0, Map("foo", "bar")); Mutation set = SetMutation("collection/key", Map("foo", "new-bar")); - MaybeDocument result = set.ApplyToRemoteDocument(base_doc, MutationResult(4)); + set.ApplyToRemoteDocument(doc, MutationResult(4)); - EXPECT_EQ(result, Doc("collection/key", 4, Map("foo", "new-bar"), - DocumentState::kCommittedMutations)); + EXPECT_EQ(doc, Doc("collection/key", 4, Map("foo", "new-bar")) + .SetHasCommittedMutations()); } TEST(MutationTest, PatchWithMutationResult) { - Document base_doc = Doc("collection/key", 0, Map("foo", "bar")); + MutableDocument doc = Doc("collection/key", 0, Map("foo", "bar")); Mutation patch = PatchMutation("collection/key", Map("foo", "new-bar")); - MaybeDocument result = - patch.ApplyToRemoteDocument(base_doc, MutationResult(4)); + patch.ApplyToRemoteDocument(doc, MutationResult(4)); - EXPECT_EQ(result, Doc("collection/key", 4, Map("foo", "new-bar"), - DocumentState::kCommittedMutations)); + EXPECT_EQ(doc, Doc("collection/key", 4, Map("foo", "new-bar")) + .SetHasCommittedMutations()); } TEST(MutationTest, Transitions) { diff --git a/Firestore/core/test/unit/model/no_document_test.cc b/Firestore/core/test/unit/model/no_document_test.cc deleted file mode 100644 index f6e076d590a..00000000000 --- a/Firestore/core/test/unit/model/no_document_test.cc +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2018 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "Firestore/core/src/model/no_document.h" - -#include "Firestore/core/src/model/unknown_document.h" -#include "absl/strings/string_view.h" -#include "gtest/gtest.h" - -namespace firebase { -namespace firestore { -namespace model { - -namespace { - -inline NoDocument MakeNoDocument(absl::string_view path, - const Timestamp& timestamp) { - return NoDocument(DocumentKey::FromPathString(path.data()), - SnapshotVersion(timestamp), - /*has_committed_mutations=*/false); -} - -} // namespace - -TEST(NoDocument, Getter) { - const NoDocument& doc = MakeNoDocument("i/am/a/path", Timestamp(123, 456)); - EXPECT_EQ(MaybeDocument::Type::NoDocument, doc.type()); - EXPECT_EQ(DocumentKey::FromPathString("i/am/a/path"), doc.key()); - EXPECT_EQ(SnapshotVersion(Timestamp(123, 456)), doc.version()); - - // NoDocument and UnknownDocument will not equal. - EXPECT_NE(NoDocument(DocumentKey::FromPathString("same/path"), - SnapshotVersion(Timestamp()), - /*has_committed_mutations=*/false), - UnknownDocument(DocumentKey::FromPathString("same/path"), - SnapshotVersion(Timestamp()))); -} - -} // namespace model -} // namespace firestore -} // namespace firebase diff --git a/Firestore/core/test/unit/model/object_value_test.cc b/Firestore/core/test/unit/model/object_value_test.cc new file mode 100644 index 00000000000..e5538f08847 --- /dev/null +++ b/Firestore/core/test/unit/model/object_value_test.cc @@ -0,0 +1,342 @@ +/* + * Copyright 2021 Google LLC + * + * 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/model/object_value.h" + +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/remote/serializer.h" +#include "Firestore/core/test/unit/testutil/testutil.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace model { + +const char kFooString[] = "foo"; +const char kBarString[] = "bar"; + +namespace { + +using absl::nullopt; +using testutil::DbId; +using testutil::Field; +using testutil::Map; +using testutil::Value; +using testutil::WrapObject; + +class ObjectValueTest : public ::testing::Test { + private: + remote::Serializer serializer{DbId()}; +}; + +TEST_F(ObjectValueTest, ExtractsFields) { + ObjectValue value = WrapObject("foo", Map("a", 1, "b", true, "c", "string")); + + ASSERT_EQ(google_firestore_v1_Value_map_value_tag, + value.Get(Field("foo"))->which_value_type); + + EXPECT_EQ(*Value(1), *value.Get(Field("foo.a"))); + EXPECT_EQ(*Value(true), *value.Get(Field("foo.b"))); + EXPECT_EQ(*Value("string"), *value.Get(Field("foo.c"))); + + EXPECT_EQ(nullopt, value.Get(Field("foo.a.b"))); + EXPECT_EQ(nullopt, value.Get(Field("bar"))); + EXPECT_EQ(nullopt, value.Get(Field("bar.a"))); +} + +TEST_F(ObjectValueTest, ExtractsFieldMask) { + ObjectValue value = + WrapObject("a", "b", "Map", + Map("a", 1, "b", true, "c", "string", "nested", Map("d", "e")), + "emptymap", Map()); + + FieldMask expected_mask = + FieldMask({Field("a"), Field("Map.a"), Field("Map.b"), Field("Map.c"), + Field("Map.nested.d"), Field("emptymap")}); + FieldMask actual_mask = value.ToFieldMask(); + + EXPECT_EQ(expected_mask, actual_mask); +} + +TEST_F(ObjectValueTest, OverwritesExistingFields) { + ObjectValue object_value = WrapObject("a", "object_value"); + EXPECT_EQ(WrapObject("a", "object_value"), object_value); + object_value.Set(Field("a"), Value("object_value")); + EXPECT_EQ(WrapObject("a", "object_value"), object_value); +} + +TEST_F(ObjectValueTest, OverwritesNestedFields) { + ObjectValue object_value = + WrapObject("a", Map("b", kFooString, "c", Map("d", kFooString))); + object_value.Set(Field("a.b"), Value(kBarString)); + object_value.Set(Field("a.c.d"), Value(kBarString)); + EXPECT_EQ(WrapObject("a", Map("b", kBarString, "c", Map("d", kBarString))), + object_value); +} + +TEST_F(ObjectValueTest, OverwritesDeeplyNestedField) { + ObjectValue object_value = WrapObject("a", Map("b", kFooString)); + object_value.Set(Field("a.b.c"), Value(kBarString)); + EXPECT_EQ(WrapObject("a", Map("b", Map("c", kBarString))), object_value); +} + +TEST_F(ObjectValueTest, OverwritesNestedObject) { + ObjectValue object_value = + WrapObject("a", Map("b", Map("c", kFooString, "d", kFooString))); + object_value.Set(Field("a.b"), Value(kBarString)); + EXPECT_EQ(WrapObject("a", Map("b", "bar")), object_value); +} + +TEST_F(ObjectValueTest, ReplacesNestedObject) { + ObjectValue object_value = WrapObject("a", Map("b", kFooString)); + object_value.Set(Field("a"), Value(Map("c", kBarString))); + EXPECT_EQ(WrapObject("a", Map("c", kBarString)), object_value); +} + +TEST_F(ObjectValueTest, ReplacesFieldWithNestedObject) { + ObjectValue object_value = WrapObject("a", 1); + object_value.Set(Field("a"), Value(Map("b", 2))); + EXPECT_EQ(WrapObject("a", Map("b", 2)), object_value); +} + +TEST_F(ObjectValueTest, AddsNewFields) { + ObjectValue object_value{}; + EXPECT_EQ(ObjectValue{}, object_value); + + object_value.Set(Field("a"), Value(1)); + EXPECT_EQ(WrapObject("a", 1), object_value); + + object_value.Set(Field("b"), Value(2)); + EXPECT_EQ(WrapObject("a", 1, "b", 2), object_value); +} + +TEST_F(ObjectValueTest, AddsMultipleFields) { + ObjectValue object_value{}; + EXPECT_EQ(ObjectValue{}, object_value); + + TransformMap data; + data[Field("a")] = Value(1); + data[Field("b")] = Value(2); + data[Field("c.d")] = Value(3); + data[Field("c.e")] = Value(4); + data[Field("c.f.g")] = Value(5); + object_value.SetAll(std::move(data)); + EXPECT_EQ( + WrapObject("a", 1, "b", 2, "c", Map("d", 3, "e", 4, "f", Map("g", 5))), + object_value); +} + +TEST_F(ObjectValueTest, AddsNestedField) { + ObjectValue object_value{}; + object_value.Set(Field("a.b"), Value(kFooString)); + object_value.Set(Field("c.d.e"), Value(kFooString)); + EXPECT_EQ(WrapObject("a", Map("b", kFooString), "c", + Map("d", Map("e", kFooString))), + object_value); +} + +TEST_F(ObjectValueTest, AddsFieldInNestedObject) { + ObjectValue object_value{}; + object_value.Set(Field("a"), Value(Map("b", kFooString))); + object_value.Set(Field("a.c"), Value(kFooString)); + EXPECT_EQ(WrapObject("a", Map("b", kFooString, "c", kFooString)), + object_value); +} + +TEST_F(ObjectValueTest, AddsTwoFieldsInNestedObject) { + ObjectValue object_value{}; + object_value.Set(Field("a.b"), Value(kFooString)); + object_value.Set(Field("a.c"), Value(kFooString)); + EXPECT_EQ(WrapObject("a", Map("b", kFooString, "c", kFooString)), + object_value); +} + +TEST_F(ObjectValueTest, AddDeeplyNestedFieldInNestedObject) { + ObjectValue object_value{}; + object_value.Set(Field("a.b.c.d.e.f"), Value(kFooString)); + EXPECT_EQ( + WrapObject("a", + Map("b", Map("c", Map("d", Map("e", Map("f", kFooString)))))), + object_value); + + object_value.Set(Field("a.a.b"), Value(kFooString)); + EXPECT_EQ( + WrapObject("a", Map("a", Map("b", kFooString), "b", + Map("c", Map("d", Map("e", Map("f", kFooString)))))), + object_value); + + object_value.Set(Field("a.c.d"), Value(kFooString)); + EXPECT_EQ( + WrapObject("a", Map("a", Map("b", kFooString), "b", + Map("c", Map("d", Map("e", Map("f", kFooString)))), + "c", Map("d", kFooString))), + object_value); +} + +TEST_F(ObjectValueTest, AddsSingleFieldInExistingObject) { + ObjectValue object_value = WrapObject("a", kFooString); + object_value.Set(Field("b"), Value(kFooString)); + EXPECT_EQ(WrapObject("a", kFooString, "b", kFooString), object_value); +} + +TEST_F(ObjectValueTest, SetsNestedFieldMultipleTimes) { + ObjectValue object_value{}; + object_value.Set(Field("a.c"), Value(kFooString)); + object_value.Set(Field("a"), Value(Map("b", kFooString))); + EXPECT_EQ(WrapObject("a", Map("b", kFooString)), object_value); +} + +TEST_F(ObjectValueTest, ImplicitlyCreatesObjects) { + ObjectValue object_value = WrapObject("a", "object_value"); + EXPECT_EQ(WrapObject("a", "object_value"), object_value); + + object_value.Set(Field("b.c.d"), Value("object_value")); + EXPECT_EQ( + WrapObject("a", "object_value", "b", Map("c", Map("d", "object_value"))), + object_value); +} + +TEST_F(ObjectValueTest, CanOverwritePrimitivesWithObjects) { + ObjectValue object_value = WrapObject("a", Map("b", "object_value")); + EXPECT_EQ(WrapObject("a", Map("b", "object_value")), object_value); + + object_value.Set(Field("a"), Value(Map("b", "object_value"))); + EXPECT_EQ(WrapObject("a", Map("b", "object_value")), object_value); +} + +TEST_F(ObjectValueTest, AddsToNestedObjects) { + ObjectValue object_value = WrapObject("a", Map("b", "object_value")); + EXPECT_EQ(WrapObject("a", Map("b", "object_value")), object_value); + + object_value.Set(Field("a.c"), Value("object_value")); + + EXPECT_EQ(WrapObject("a", Map("b", "object_value", "c", "object_value")), + object_value); +} + +TEST_F(ObjectValueTest, DeletesKey) { + ObjectValue object_value = WrapObject("a", 1, "b", 2); + EXPECT_EQ(WrapObject("a", 1, "b", 2), object_value); + + object_value.Delete(Field("a")); + + EXPECT_EQ(WrapObject("b", 2), object_value); + + object_value.Delete(Field("b")); + EXPECT_EQ(ObjectValue(), object_value); +} + +TEST_F(ObjectValueTest, DeletesMultipleKeys) { + ObjectValue object_value = + WrapObject("a", 1, "b", 2, "c", Map("d", 3, "e", 4)); + + TransformMap data; + data[Field("a")] = absl::nullopt; + data[Field("b")] = absl::nullopt; + data[Field("c.d")] = absl::nullopt; + object_value.SetAll(std::move(data)); + + EXPECT_EQ(WrapObject("c", Map("e", 4)), object_value); +} + +TEST_F(ObjectValueTest, DeletesHandleMissingKeys) { + ObjectValue object_value = WrapObject("a", Map("b", 1, "c", 2)); + EXPECT_EQ(WrapObject("a", Map("b", 1, "c", 2)), object_value); + + object_value.Delete(Field("b")); + object_value.Delete(Field("a.d")); + EXPECT_EQ(WrapObject("a", Map("b", 1, "c", 2)), object_value); + + object_value.Delete(Field("a.b.c")); + EXPECT_EQ(WrapObject("a", Map("b", 1, "c", 2)), object_value); +} + +TEST_F(ObjectValueTest, DeletesNestedKeys) { + auto orig = Map("a", Map("b", 1, "c", Map("d", 2, "e", 3))); + ObjectValue object_value = WrapObject(std::move(orig)); + object_value.Delete(Field("a.c.d")); + EXPECT_EQ(WrapObject(Map("a", Map("b", 1, "c", Map("e", 3)))), object_value); + + object_value.Delete(Field("a.c")); + EXPECT_EQ(WrapObject(Map("a", Map("b", 1))), object_value); + + object_value.Delete(Field("a")); + EXPECT_EQ(ObjectValue(), object_value); +} + +TEST_F(ObjectValueTest, DeletesNestedObject) { + ObjectValue object_value = WrapObject( + "a", Map("b", Map("c", kFooString, "d", kFooString), "f", kFooString)); + object_value.Delete(Field("a.b")); + EXPECT_EQ(WrapObject("a", Map("f", kFooString)), object_value); + object_value.Delete(Field("a.f")); + EXPECT_EQ(WrapObject("a", Map()), object_value); +} + +TEST_F(ObjectValueTest, AddsAndDeletesField) { + ObjectValue object_value{}; + object_value.Set(Field(kFooString), Value(kFooString)); + object_value.Delete(Field(kFooString)); + EXPECT_EQ(WrapObject(), object_value); +} + +TEST_F(ObjectValueTest, AddsAndDeletesMultipleFields) { + ObjectValue object_value = WrapObject("b", 2, "c", 3); + TransformMap data; + data[Field("a")] = Value(1); + data[Field("b")] = absl::nullopt; + object_value.SetAll(std::move(data)); + EXPECT_EQ(WrapObject("a", 1, "c", 3), object_value); +} + +TEST_F(ObjectValueTest, AddsAndDeletesNestedField) { + ObjectValue object_value{}; + object_value.Set(Field("a.b.c"), Value(kFooString)); + object_value.Set(Field("a.b.d"), Value(kFooString)); + object_value.Set(Field("f.g"), Value(kFooString)); + object_value.Set(Field("h"), Value(kFooString)); + object_value.Delete(Field("a.b.c")); + object_value.Delete(Field("h")); + EXPECT_EQ(WrapObject("a", Map("b", Map("d", kFooString)), "f", + Map("g", kFooString)), + object_value); +} + +TEST_F(ObjectValueTest, MergesExistingObject) { + ObjectValue object_value = WrapObject("a", Map("b", kFooString)); + object_value.Set(Field("a.c"), Value(kFooString)); + EXPECT_EQ(WrapObject("a", Map("b", kFooString, "c", kFooString)), + object_value); +} + +TEST_F(ObjectValueTest, DoesNotRequireSortedValues) { + ObjectValue object_value = WrapObject("c", 2, "a", 1); + EXPECT_EQ(*Value(2), *object_value.Get(Field("c"))); +} + +TEST_F(ObjectValueTest, DoesNotRequireSortedInserts) { + ObjectValue object_value{}; + object_value.Set(Field("nested"), + Map("c", 2, "nested", Map("c", 2, "a", 1), "a", 1)); + EXPECT_EQ(*Value(2), *object_value.Get(Field("nested.c"))); + EXPECT_EQ(*Value(2), *object_value.Get(Field("nested.nested.c"))); +} + +} // namespace + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/model/precondition_test.cc b/Firestore/core/test/unit/model/precondition_test.cc index 2f5acace2aa..14138f1396d 100644 --- a/Firestore/core/test/unit/model/precondition_test.cc +++ b/Firestore/core/test/unit/model/precondition_test.cc @@ -16,8 +16,7 @@ #include "Firestore/core/src/model/precondition.h" -#include "Firestore/core/src/model/document.h" -#include "Firestore/core/src/model/no_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/snapshot_version.h" #include "Firestore/core/test/unit/testutil/testutil.h" #include "gtest/gtest.h" @@ -32,11 +31,11 @@ TEST(Precondition, None) { EXPECT_TRUE(none.is_none()); EXPECT_EQ(SnapshotVersion::None(), none.update_time()); - NoDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567); - Document doc = testutil::Doc("bar/doc", 7654321); + MutableDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567); + MutableDocument doc = testutil::Doc("bar/doc", 7654321); EXPECT_TRUE(none.IsValidFor(deleted_doc)); EXPECT_TRUE(none.IsValidFor(doc)); - EXPECT_TRUE(none.IsValidFor(absl::nullopt)); + EXPECT_TRUE(none.IsValidFor(testutil::InvalidDoc("foo/doc"))); } TEST(Precondition, Exists) { @@ -49,14 +48,14 @@ TEST(Precondition, Exists) { EXPECT_EQ(SnapshotVersion::None(), exists.update_time()); EXPECT_EQ(SnapshotVersion::None(), no_exists.update_time()); - NoDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567); - Document doc = testutil::Doc("bar/doc", 7654321); + MutableDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567); + MutableDocument doc = testutil::Doc("bar/doc", 7654321); EXPECT_FALSE(exists.IsValidFor(deleted_doc)); EXPECT_TRUE(exists.IsValidFor(doc)); - EXPECT_FALSE(exists.IsValidFor(absl::nullopt)); + EXPECT_FALSE(exists.IsValidFor(testutil::InvalidDoc("foo/doc"))); EXPECT_TRUE(no_exists.IsValidFor(deleted_doc)); EXPECT_FALSE(no_exists.IsValidFor(doc)); - EXPECT_TRUE(no_exists.IsValidFor(absl::nullopt)); + EXPECT_TRUE(no_exists.IsValidFor(testutil::InvalidDoc("foo/doc"))); } TEST(Precondition, UpdateTime) { @@ -66,13 +65,13 @@ TEST(Precondition, UpdateTime) { EXPECT_FALSE(update_time.is_none()); EXPECT_EQ(testutil::Version(1234567), update_time.update_time()); - NoDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567); - Document not_match = testutil::Doc("bar/doc", 7654321); - Document match = testutil::Doc("baz/doc", 1234567); + MutableDocument deleted_doc = testutil::DeletedDoc("foo/doc", 1234567); + MutableDocument not_match = testutil::Doc("bar/doc", 7654321); + MutableDocument match = testutil::Doc("baz/doc", 1234567); EXPECT_FALSE(update_time.IsValidFor(deleted_doc)); EXPECT_FALSE(update_time.IsValidFor(not_match)); EXPECT_TRUE(update_time.IsValidFor(match)); - EXPECT_FALSE(update_time.IsValidFor(absl::nullopt)); + EXPECT_FALSE(update_time.IsValidFor(testutil::InvalidDoc("foo/doc"))); } } // namespace model diff --git a/Firestore/core/test/unit/model/transform_operation_test.cc b/Firestore/core/test/unit/model/transform_operation_test.cc index 4e59bd9adfc..892714dd403 100644 --- a/Firestore/core/test/unit/model/transform_operation_test.cc +++ b/Firestore/core/test/unit/model/transform_operation_test.cc @@ -16,7 +16,6 @@ #include "Firestore/core/src/model/transform_operation.h" -#include "Firestore/core/src/model/field_value.h" #include "Firestore/core/test/unit/testutil/testutil.h" #include "gtest/gtest.h" diff --git a/Firestore/core/test/unit/model/value_util_test.cc b/Firestore/core/test/unit/model/value_util_test.cc new file mode 100644 index 00000000000..0a0a9baa33d --- /dev/null +++ b/Firestore/core/test/unit/model/value_util_test.cc @@ -0,0 +1,373 @@ +/* + * Copyright 2021 Google LLC + * + * 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 + +#include "Firestore/core/include/firebase/firestore/geo_point.h" +#include "Firestore/core/src/model/database_id.h" +#include "Firestore/core/src/model/server_timestamp_util.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/nanopb/message.h" +#include "Firestore/core/src/remote/serializer.h" +#include "Firestore/core/src/util/comparison.h" +#include "Firestore/core/src/util/defer.h" +#include "Firestore/core/test/unit/testutil/equals_tester.h" +#include "Firestore/core/test/unit/testutil/testutil.h" +#include "Firestore/core/test/unit/testutil/time_testing.h" +#include "absl/base/casts.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace model { +namespace { + +using model::EncodeServerTimestamp; +using model::RefValue; +using nanopb::Message; +using testutil::Array; +using testutil::BlobValue; +using testutil::DbId; +using testutil::kCanonicalNanBits; +using testutil::Key; +using testutil::Map; +using testutil::time_point; +using testutil::Value; +using util::ComparisonResult; + +namespace { + +#if __APPLE__ +uint64_t ToBits(double value) { + return absl::bit_cast(value); +} +#endif // __APPLE__ + +double ToDouble(uint64_t value) { + return absl::bit_cast(value); +} + +// All permutations of the 51 other non-MSB significand bits are also NaNs. +const uint64_t kAlternateNanBits = 0x7fff000000000000ULL; + +const time_point kDate1 = testutil::MakeTimePoint(2016, 5, 20, 10, 20, 0); +const Timestamp kTimestamp1{1463739600, 0}; + +const time_point kDate2 = testutil::MakeTimePoint(2016, 10, 21, 15, 32, 0); +const Timestamp kTimestamp2{1477063920, 0}; + +} // namespace + +class ValueUtilTest : public ::testing::Test { + public: + template + void Add(std::vector>& groups, + Args... values) { + groups.emplace_back(Array(std::forward(values)...)); + } + + void VerifyEquality(Message& left, + Message& right, + bool expected_equals) { + for (pb_size_t i = 0; i < left->values_count; ++i) { + for (pb_size_t j = 0; j < right->values_count; ++j) { + if (expected_equals) { + EXPECT_EQ(left->values[i], right->values[j]); + } else { + EXPECT_NE(left->values[i], right->values[j]); + } + } + } + } + + void VerifyOrdering(Message& left, + Message& right, + ComparisonResult expected_result) { + for (pb_size_t i = 0; i < left->values_count; ++i) { + for (pb_size_t j = 0; j < right->values_count; ++j) { + EXPECT_EQ(expected_result, Compare(left->values[i], right->values[j])) + << "Order check failed for '" << CanonicalId(left->values[i]) + << "' and '" << CanonicalId(right->values[j]) << "' (expected " + << static_cast(expected_result) << ")"; + EXPECT_EQ(util::ReverseOrder(expected_result), + Compare(right->values[j], left->values[i])) + << "Reverse order check failed for '" + << CanonicalId(left->values[i]) << "' and '" + << CanonicalId(right->values[j]) << "' (expected " + << static_cast(util::ReverseOrder(expected_result)) << ")"; + } + } + } + + void VerifyCanonicalId(nanopb::Message value, + const std::string& expected_canonical_id) { + std::string actual_canonical_id = CanonicalId(*value); + EXPECT_EQ(expected_canonical_id, actual_canonical_id); + } + + void VerifyDeepClone(nanopb::Message value) { + nanopb::Message clone1; + + [&] { + nanopb::Message clone2 = DeepClone(*value); + EXPECT_EQ(*value, *clone2); + clone1 = DeepClone(*clone2); + }(); + + // `clone2` is destroyed at this point, but `clone1` should be still valid. + EXPECT_EQ(*value, *clone1); + } + + private: + remote::Serializer serializer{DbId()}; +}; + +TEST(FieldValueTest, ValueHelpers) { + // Validates that the Value helpers in testutil produce the right types + auto bool_value = Value(true); + ASSERT_EQ(GetTypeOrder(*bool_value), TypeOrder::kBoolean); + EXPECT_EQ(bool_value->boolean_value, true); + + auto int_value = Value(5); + ASSERT_EQ(GetTypeOrder(*int_value), TypeOrder::kNumber); + EXPECT_EQ(int_value->integer_value, 5); + + auto long_value = Value(std::numeric_limits::max()); + ASSERT_EQ(GetTypeOrder(*long_value), TypeOrder::kNumber); + EXPECT_EQ(long_value->integer_value, std::numeric_limits::max()); + + auto long_long_value = Value(std::numeric_limits::max()); + ASSERT_EQ(GetTypeOrder(*long_long_value), TypeOrder::kNumber); + EXPECT_EQ(long_long_value->integer_value, + std::numeric_limits::max()); + + auto double_value = Value(2.0); + ASSERT_EQ(GetTypeOrder(*double_value), TypeOrder::kNumber); + EXPECT_EQ(double_value->double_value, 2.0); +} + +#if __APPLE__ +// Validates that NSNumber/CFNumber normalize NaNs to the same values that +// Firestore does. This uses CoreFoundation's CFNumber instead of NSNumber just +// to keep the test in a single file. +TEST(FieldValueTest, CanonicalBitsAreCanonical) { + double input = ToDouble(kAlternateNanBits); + CFNumberRef number = CFNumberCreate(nullptr, kCFNumberDoubleType, &input); + util::Defer cleanup([&] { util::SafeCFRelease(number); }); + + double actual = 0.0; + CFNumberGetValue(number, kCFNumberDoubleType, &actual); + + ASSERT_EQ(kCanonicalNanBits, ToBits(actual)); +} +#endif // __APPLE__ + +TEST_F(ValueUtilTest, Equality) { + // Create a matrix that defines an equality group. The outer vector has + // multiple rows and each row can have an arbitrary number of entries. + // The elements within a row must equal each other, but not be equal + // to all elements of other rows. + std::vector> equals_group; + + Add(equals_group, nullptr, nullptr); + Add(equals_group, false, false); + Add(equals_group, true, true); + Add(equals_group, std::numeric_limits::quiet_NaN(), + ToDouble(kCanonicalNanBits), ToDouble(kAlternateNanBits), std::nan("1"), + std::nan("2")); + // -0.0 and 0.0 compare the same but are not equal. + Add(equals_group, -0.0); + Add(equals_group, 0.0); + Add(equals_group, 1, 1LL); + // Doubles and Longs aren't equal (even though they compare same). + Add(equals_group, 1.0, 1.0); + Add(equals_group, 1.1, 1.1); + Add(equals_group, BlobValue(0, 1, 1)); + Add(equals_group, BlobValue(0, 1)); + Add(equals_group, "string", "string"); + Add(equals_group, "strin"); + Add(equals_group, std::string("strin\0", 6)); + // latin small letter e + combining acute accent + Add(equals_group, "e\u0301b"); + // latin small letter e with acute accent + Add(equals_group, "\u00e9a"); + Add(equals_group, Timestamp::FromTimePoint(kDate1), kTimestamp1); + Add(equals_group, Timestamp::FromTimePoint(kDate2), kTimestamp2); + // NOTE: ServerTimestampValues can't be parsed via . + Add(equals_group, EncodeServerTimestamp(kTimestamp1, absl::nullopt), + EncodeServerTimestamp(kTimestamp1, absl::nullopt)); + Add(equals_group, EncodeServerTimestamp(kTimestamp2, absl::nullopt)); + Add(equals_group, GeoPoint(0, 1), GeoPoint(0, 1)); + Add(equals_group, GeoPoint(1, 0)); + Add(equals_group, RefValue(DbId(), Key("coll/doc1")), + RefValue(DbId(), Key("coll/doc1"))); + Add(equals_group, RefValue(DbId(), Key("coll/doc2"))); + Add(equals_group, RefValue(DbId("project/baz"), Key("coll/doc2"))); + Add(equals_group, Array("foo", "bar"), Array("foo", "bar")); + Add(equals_group, Array("foo", "bar", "baz")); + Add(equals_group, Array("foo")); + Add(equals_group, Map("bar", 1, "foo", 2), Map("bar", 1, "foo", 2)); + Add(equals_group, Map("bar", 2, "foo", 1)); + Add(equals_group, Map("bar", 1)); + Add(equals_group, Map("foo", 1)); + + for (size_t i = 0; i < equals_group.size(); ++i) { + for (size_t j = i; j < equals_group.size(); ++j) { + VerifyEquality(equals_group[i], equals_group[j], + /* expected_equals= */ i == j); + } + } +} + +TEST_F(ValueUtilTest, Ordering) { + // Create a matrix that defines a comparison group. The outer vector has + // multiple rows and each row can have an arbitrary number of entries. + // The elements within a row must compare equal to each other, but order after + // all elements in previous groups and before all elements in later groups. + std::vector> comparison_groups; + + // null first + Add(comparison_groups, nullptr); + + // booleans + Add(comparison_groups, false); + Add(comparison_groups, true); + + // numbers + Add(comparison_groups, -1e20); + Add(comparison_groups, std::numeric_limits::min()); + Add(comparison_groups, -0.1); + // Zeros all compare the same. + Add(comparison_groups, -0.0, 0.0, 0L); + Add(comparison_groups, 0.1); + // Doubles and longs Compare() the same. + Add(comparison_groups, 1.0, 1L); + Add(comparison_groups, std::numeric_limits::max()); + Add(comparison_groups, 1e20); + + // dates + Add(comparison_groups, kTimestamp1); + Add(comparison_groups, kTimestamp2); + + // server timestamps come after all concrete timestamps. + // NOTE: server timestamps can't be parsed with . + Add(comparison_groups, EncodeServerTimestamp(kTimestamp1, absl::nullopt)); + Add(comparison_groups, EncodeServerTimestamp(kTimestamp2, absl::nullopt)); + + // strings + Add(comparison_groups, ""); + Add(comparison_groups, "\001\ud7ff\ue000\uffff"); + Add(comparison_groups, "(╯°□°)╯︵ ┻━┻"); + Add(comparison_groups, "a"); + Add(comparison_groups, std::string("abc\0 def", 8)); + Add(comparison_groups, "abc def"); + // latin small letter e + combining acute accent + latin small letter b + Add(comparison_groups, "e\u0301b"); + Add(comparison_groups, "æ"); + // latin small letter e with acute accent + latin small letter a + Add(comparison_groups, "\u00e9a"); + + // blobs + Add(comparison_groups, BlobValue()); + Add(comparison_groups, BlobValue(0)); + Add(comparison_groups, BlobValue(0, 1, 2, 3, 4)); + Add(comparison_groups, BlobValue(0, 1, 2, 4, 3)); + Add(comparison_groups, BlobValue(255)); + + // resource names + Add(comparison_groups, RefValue(DbId("p1/d1"), Key("c1/doc1"))); + Add(comparison_groups, RefValue(DbId("p1/d1"), Key("c1/doc2"))); + Add(comparison_groups, RefValue(DbId("p1/d1"), Key("c10/doc1"))); + Add(comparison_groups, RefValue(DbId("p1/d1"), Key("c2/doc1"))); + Add(comparison_groups, RefValue(DbId("p1/d2"), Key("c1/doc1"))); + Add(comparison_groups, RefValue(DbId("p2/d1"), Key("c1/doc1"))); + + // geo points + Add(comparison_groups, GeoPoint(-90, -180)); + Add(comparison_groups, GeoPoint(-90, 0)); + Add(comparison_groups, GeoPoint(-90, 180)); + Add(comparison_groups, GeoPoint(0, -180)); + Add(comparison_groups, GeoPoint(0, 0)); + Add(comparison_groups, GeoPoint(0, 180)); + Add(comparison_groups, GeoPoint(1, -180)); + Add(comparison_groups, GeoPoint(1, 0)); + Add(comparison_groups, GeoPoint(1, 180)); + Add(comparison_groups, GeoPoint(90, -180)); + Add(comparison_groups, GeoPoint(90, 0)); + Add(comparison_groups, GeoPoint(90, 180)); + + // arrays + Add(comparison_groups, Array("bar")); + Add(comparison_groups, Array("foo", 1)); + Add(comparison_groups, Array("foo", 2)); + Add(comparison_groups, Array("foo", "0")); + + // objects + Add(comparison_groups, Map("bar", 0)); + Add(comparison_groups, Map("bar", 0, "foo", 1)); + Add(comparison_groups, Map("foo", 1)); + Add(comparison_groups, Map("foo", 2)); + Add(comparison_groups, Map("foo", "0")); + + for (size_t i = 0; i < comparison_groups.size(); ++i) { + for (size_t j = i; j < comparison_groups.size(); ++j) { + VerifyOrdering( + comparison_groups[i], comparison_groups[j], + i == j ? ComparisonResult::Same : ComparisonResult::Ascending); + } + } +} + +TEST_F(ValueUtilTest, CanonicalId) { + VerifyCanonicalId(Value(nullptr), "null"); + VerifyCanonicalId(Value(true), "true"); + VerifyCanonicalId(Value(false), "false"); + VerifyCanonicalId(Value(1), "1"); + VerifyCanonicalId(Value(1.0), "1.0"); + VerifyCanonicalId(Value(Timestamp(30, 1000)), "time(30,1000)"); + VerifyCanonicalId(Value("a"), "a"); + VerifyCanonicalId(Value(std::string("a\0b", 3)), std::string("a\0b", 3)); + VerifyCanonicalId(Value(BlobValue(1, 2, 3)), "010203"); + VerifyCanonicalId(RefValue(DbId("p1/d1"), Key("c1/doc1")), "c1/doc1"); + VerifyCanonicalId(Value(GeoPoint(30, 60)), "geo(30.0,60.0)"); + VerifyCanonicalId(Value(Array(1, 2, 3)), "[1,2,3]"); + VerifyCanonicalId(Map("a", 1, "b", 2, "c", "3"), "{a:1,b:2,c:3}"); + VerifyCanonicalId(Map("a", Array("b", Map("c", GeoPoint(30, 60)))), + "{a:[b,{c:geo(30.0,60.0)}]}"); +} + +TEST_F(ValueUtilTest, DeepClone) { + VerifyDeepClone(Value(nullptr)); + VerifyDeepClone(Value(true)); + VerifyDeepClone(Value(false)); + VerifyDeepClone(Value(1)); + VerifyDeepClone(Value(1.0)); + VerifyDeepClone(Value(Timestamp(30, 1000))); + VerifyDeepClone(Value("a")); + VerifyDeepClone(Value(std::string("a\0b", 3))); + VerifyDeepClone(Value(BlobValue(1, 2, 3))); + VerifyDeepClone(RefValue(DbId("p1/d1"), Key("c1/doc1"))); + VerifyDeepClone(Value(GeoPoint(30, 60))); + VerifyDeepClone(Value(Array(1, 2, 3))); + VerifyDeepClone(Map("a", 1, "b", 2, "c", "3")); + VerifyDeepClone(Map("a", Array("b", Map("c", GeoPoint(30, 60))))); +} + +} // namespace + +} // namespace model +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/nanopb/nanopb_util_test.cc b/Firestore/core/test/unit/nanopb/nanopb_util_test.cc new file mode 100644 index 00000000000..de7249859f9 --- /dev/null +++ b/Firestore/core/test/unit/nanopb/nanopb_util_test.cc @@ -0,0 +1,55 @@ +/* + * Copyright 2021 Google LLC + * + * 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/nanopb/nanopb_util.h" +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" +#include "Firestore/core/src/nanopb/message.h" +#include "Firestore/core/test/unit/testutil/testutil.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace nanopb { +namespace { + +using testing::ElementsAre; +using testutil::Value; + +TEST(NanopbUtilTest, SetsRepeatedField) { + Message m; + std::vector values{ + *Value(1).release(), *Value(2).release(), *Value(3).release()}; + SetRepeatedField(&m->values, &m->values_count, values); + EXPECT_EQ(values, std::vector( + m->values, m->values + m->values_count)); +} + +TEST(NanopbUtilTest, SetsRepeatedFieldWithConverter) { + Message m; + std::vector values{1, 2, 3}; + SetRepeatedField(&m->values, &m->values_count, values, + [](const int& v) { return *Value(v).release(); }); + EXPECT_THAT(std::vector( + m->values, m->values + m->values_count), + ElementsAre(*Value(1).release(), *Value(2).release(), + *Value(3).release())); +} + +} // namespace +} // namespace nanopb +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/remote/datastore_test.cc b/Firestore/core/test/unit/remote/datastore_test.cc index c7e286649ab..37f72756d8a 100644 --- a/Firestore/core/test/unit/remote/datastore_test.cc +++ b/Firestore/core/test/unit/remote/datastore_test.cc @@ -22,6 +22,7 @@ #include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/Protos/nanopb/google/firestore/v1/firestore.nanopb.h" +#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/mutation.h" #include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/nanopb/nanopb_util.h" @@ -54,7 +55,6 @@ using auth::CredentialsProvider; using core::DatabaseInfo; using model::DatabaseId; using model::Document; -using model::MaybeDocument; using nanopb::MakeArray; using nanopb::Message; using testing::Not; @@ -84,8 +84,9 @@ grpc::ByteBuffer MakeFakeDocument(const std::string& doc_name) { MakeArray(doc.fields_count); google_firestore_v1_Document_FieldsEntry& entry = doc.fields[0]; + Message value = Value("bar"); entry.key = serializer.EncodeString("foo"); - entry.value = serializer.EncodeFieldValue(Value("bar")); + entry.value = *value.release(); return MakeByteBuffer(response); } @@ -216,15 +217,15 @@ TEST_F(DatastoreTest, CommitMutationsSuccess) { TEST_F(DatastoreTest, LookupDocumentsOneSuccessfulRead) { bool done = false; - std::vector resulting_docs; + std::vector resulting_docs; Status resulting_status; datastore->LookupDocuments( - {}, [&](const StatusOr>& maybe_documents) { + {}, [&](const StatusOr>& documents) { done = true; - if (maybe_documents.ok()) { - resulting_docs = maybe_documents.ValueOrDie(); + if (documents.ok()) { + resulting_docs = documents.ValueOrDie(); } - resulting_status = maybe_documents.status(); + resulting_status = documents.status(); }); // Make sure Auth has a chance to run. worker_queue->EnqueueBlocking([] {}); @@ -237,21 +238,21 @@ TEST_F(DatastoreTest, LookupDocumentsOneSuccessfulRead) { EXPECT_TRUE(done); EXPECT_EQ(resulting_docs.size(), 1); - EXPECT_EQ(resulting_docs[0].key().ToString(), "foo/1"); + EXPECT_EQ(resulting_docs[0]->key().ToString(), "foo/1"); EXPECT_TRUE(resulting_status.ok()); } TEST_F(DatastoreTest, LookupDocumentsTwoSuccessfulReads) { bool done = false; - std::vector resulting_docs; + std::vector resulting_docs; Status resulting_status; datastore->LookupDocuments( - {}, [&](const StatusOr>& maybe_documents) { + {}, [&](const StatusOr>& documents) { done = true; - if (maybe_documents.ok()) { - resulting_docs = maybe_documents.ValueOrDie(); + if (documents.ok()) { + resulting_docs = documents.ValueOrDie(); } - resulting_status = maybe_documents.status(); + resulting_status = documents.status(); }); // Make sure Auth has a chance to run. worker_queue->EnqueueBlocking([] {}); @@ -265,8 +266,8 @@ TEST_F(DatastoreTest, LookupDocumentsTwoSuccessfulReads) { EXPECT_TRUE(done); EXPECT_EQ(resulting_docs.size(), 2); - EXPECT_EQ(resulting_docs[0].key().ToString(), "foo/1"); - EXPECT_EQ(resulting_docs[1].key().ToString(), "foo/2"); + EXPECT_EQ(resulting_docs[0]->key().ToString(), "foo/1"); + EXPECT_EQ(resulting_docs[1]->key().ToString(), "foo/2"); EXPECT_TRUE(resulting_status.ok()); } @@ -293,9 +294,9 @@ TEST_F(DatastoreTest, LookupDocumentsErrorBeforeFirstRead) { bool done = false; Status resulting_status; datastore->LookupDocuments( - {}, [&](const StatusOr>& maybe_documents) { + {}, [&](const StatusOr>& documents) { done = true; - resulting_status = maybe_documents.status(); + resulting_status = documents.status(); }); // Make sure Auth has a chance to run. worker_queue->EnqueueBlocking([] {}); @@ -311,12 +312,12 @@ TEST_F(DatastoreTest, LookupDocumentsErrorBeforeFirstRead) { TEST_F(DatastoreTest, LookupDocumentsErrorAfterFirstRead) { bool done = false; - std::vector resulting_docs; + std::vector resulting_docs; Status resulting_status; datastore->LookupDocuments( - {}, [&](const StatusOr>& maybe_documents) { + {}, [&](const StatusOr>& documents) { done = true; - resulting_status = maybe_documents.status(); + resulting_status = documents.status(); }); // Make sure Auth has a chance to run. worker_queue->EnqueueBlocking([] {}); @@ -349,8 +350,8 @@ TEST_F(DatastoreTest, LookupDocumentsAuthFailure) { Status resulting_status; datastore->LookupDocuments( - {}, [&](const StatusOr>& maybe_documents) { - resulting_status = maybe_documents.status(); + {}, [&](const StatusOr>& documents) { + resulting_status = documents.status(); }); worker_queue->EnqueueBlocking([] {}); EXPECT_FALSE(resulting_status.ok()); diff --git a/Firestore/core/test/unit/remote/remote_event_test.cc b/Firestore/core/test/unit/remote/remote_event_test.cc index b4411453636..8764a3aae90 100644 --- a/Firestore/core/test/unit/remote/remote_event_test.cc +++ b/Firestore/core/test/unit/remote/remote_event_test.cc @@ -23,7 +23,6 @@ #include "Firestore/core/src/local/target_data.h" #include "Firestore/core/src/model/document_key.h" -#include "Firestore/core/src/model/no_document.h" #include "Firestore/core/src/model/types.h" #include "Firestore/core/src/remote/existence_filter.h" #include "Firestore/core/src/remote/watch_change.h" @@ -38,12 +37,9 @@ namespace remote { using local::QueryPurpose; using local::TargetData; -using model::Document; using model::DocumentKey; using model::DocumentKeySet; -using model::DocumentState; -using model::MaybeDocument; -using model::NoDocument; +using model::MutableDocument; using model::SnapshotVersion; using model::TargetId; using nanopb::ByteString; @@ -70,7 +66,7 @@ std::unique_ptr MakeDocChange( std::vector updated, std::vector removed, DocumentKey key, - const MaybeDocument& doc) { + const MutableDocument& doc) { return absl::make_unique( std::move(updated), std::move(removed), std::move(key), doc); } @@ -246,11 +242,11 @@ TEST_F(RemoteEventTest, WillAccumulateDocumentAddedAndRemovedEvents) { std::unordered_map target_map = ActiveQueries({1, 2, 3, 4, 5, 6}); - Document existing_doc = Doc("docs/1", 1, Map("value", 1)); + MutableDocument existing_doc = Doc("docs/1", 1, Map("value", 1)); auto change1 = MakeDocChange({1, 2, 3}, {4, 5, 6}, existing_doc.key(), existing_doc); - Document new_doc = Doc("docs/2", 2, Map("value", 2)); + MutableDocument new_doc = Doc("docs/2", 2, Map("value", 2)); auto change2 = MakeDocChange({1, 4}, {2, 6}, new_doc.key(), new_doc); // Create a remote event that includes both `change1` and `change2` as well as @@ -303,11 +299,11 @@ TEST_F(RemoteEventTest, WillAccumulateDocumentAddedAndRemovedEvents) { TEST_F(RemoteEventTest, WillIgnoreEventsForPendingTargets) { std::unordered_map target_map = ActiveQueries({1}); - Document doc1 = Doc("docs/1", 1, Map("value", 1)); + MutableDocument doc1 = Doc("docs/1", 1, Map("value", 1)); auto change1 = MakeDocChange({1}, {}, doc1.key(), doc1); auto change2 = MakeTargetChange(WatchTargetChangeState::Removed, {1}); auto change3 = MakeTargetChange(WatchTargetChangeState::Added, {1}); - Document doc2 = Doc("docs/2", 2, Map("value", 2)); + MutableDocument doc2 = Doc("docs/2", 2, Map("value", 2)); auto change4 = MakeDocChange({1}, {}, doc2.key(), doc2); // We're waiting for the unwatch and watch ack @@ -329,7 +325,7 @@ TEST_F(RemoteEventTest, WillIgnoreEventsForPendingTargets) { TEST_F(RemoteEventTest, WillIgnoreEventsForRemovedTargets) { std::unordered_map target_map = ActiveQueries({}); - Document doc1 = Doc("docs/1", 1, Map("value", 1)); + MutableDocument doc1 = Doc("docs/1", 1, Map("value", 1)); auto change1 = MakeDocChange({1}, {}, doc1.key(), doc1); auto change2 = MakeTargetChange(WatchTargetChangeState::Removed, {1}); @@ -350,17 +346,17 @@ TEST_F(RemoteEventTest, WillIgnoreEventsForRemovedTargets) { TEST_F(RemoteEventTest, WillKeepResetMappingEvenWithUpdates) { std::unordered_map target_map = ActiveQueries({1}); - Document doc1 = Doc("docs/1", 1, Map("value", 1)); + MutableDocument doc1 = Doc("docs/1", 1, Map("value", 1)); auto change1 = MakeDocChange({1}, {}, doc1.key(), doc1); // Reset stream, ignoring doc1 auto change2 = MakeTargetChange(WatchTargetChangeState::Reset, {1}); // Add doc2, doc3 - Document doc2 = Doc("docs/2", 2, Map("value", 2)); + MutableDocument doc2 = Doc("docs/2", 2, Map("value", 2)); auto change3 = MakeDocChange({1}, {}, doc2.key(), doc2); - Document doc3 = Doc("docs/3", 3, Map("value", 3)); + MutableDocument doc3 = Doc("docs/3", 3, Map("value", 3)); auto change4 = MakeDocChange({1}, {}, doc3.key(), doc3); // Remove doc2 again, should not show up in reset mapping @@ -410,10 +406,10 @@ TEST_F(RemoteEventTest, WillHandleSingleReset) { TEST_F(RemoteEventTest, WillHandleTargetAddAndRemovalInSameBatch) { std::unordered_map target_map = ActiveQueries({1, 2}); - Document doc1a = Doc("docs/1", 1, Map("value", 1)); + MutableDocument doc1a = Doc("docs/1", 1, Map("value", 1)); auto change1 = MakeDocChange({1}, {2}, doc1a.key(), doc1a); - Document doc1b = Doc("docs/1", 1, Map("value", 2)); + MutableDocument doc1b = Doc("docs/1", 1, Map("value", 2)); auto change2 = MakeDocChange({2}, {1}, doc1b.key(), doc1b); RemoteEvent event = CreateRemoteEvent( @@ -456,14 +452,14 @@ TEST_F(RemoteEventTest, TargetCurrentChangeWillMarkTheTargetCurrent) { TEST_F(RemoteEventTest, TargetAddedChangeWillResetPreviousState) { std::unordered_map target_map = ActiveQueries({1, 3}); - Document doc1 = Doc("docs/1", 1, Map("value", 1)); + MutableDocument doc1 = Doc("docs/1", 1, Map("value", 1)); auto change1 = MakeDocChange({1, 3}, {2}, doc1.key(), doc1); auto change2 = MakeTargetChange(WatchTargetChangeState::Current, {1, 2, 3}, resume_token1_); auto change3 = MakeTargetChange(WatchTargetChangeState::Removed, {1}); auto change4 = MakeTargetChange(WatchTargetChangeState::Removed, {2}); auto change5 = MakeTargetChange(WatchTargetChangeState::Added, {1}); - Document doc2 = Doc("docs/2", 2, Map("value", 2)); + MutableDocument doc2 = Doc("docs/2", 2, Map("value", 2)); auto change6 = MakeDocChange({1}, {3}, doc2.key(), doc2); std::unordered_map outstanding_responses{{1, 2}, {2, 1}}; @@ -519,9 +515,9 @@ TEST_F(RemoteEventTest, NoChangeWillStillMarkTheAffectedTargets) { TEST_F(RemoteEventTest, ExistenceFilterMismatchClearsTarget) { std::unordered_map target_map = ActiveQueries({1, 2}); - Document doc1 = Doc("docs/1", 1, Map("value", 1)); + MutableDocument doc1 = Doc("docs/1", 1, Map("value", 1)); auto change1 = MakeDocChange({1}, {}, doc1.key(), doc1); - Document doc2 = Doc("docs/2", 2, Map("value", 2)); + MutableDocument doc2 = Doc("docs/2", 2, Map("value", 2)); auto change2 = MakeDocChange({1}, {}, doc2.key(), doc2); auto change3 = MakeTargetChange(WatchTargetChangeState::Current, {1}, resume_token1_); @@ -576,7 +572,7 @@ TEST_F(RemoteEventTest, ExistenceFilterMismatchRemovesCurrentChanges) { WatchTargetChangeState::Current, {1}, resume_token1_}; aggregator.HandleTargetChange(mark_current); - Document doc1 = Doc("docs/1", 1, Map("value", 1)); + MutableDocument doc1 = Doc("docs/1", 1, Map("value", 1)); DocumentWatchChange add_doc{{1}, {}, doc1.key(), doc1}; aggregator.HandleDocumentChange(add_doc); @@ -602,9 +598,9 @@ TEST_F(RemoteEventTest, ExistenceFilterMismatchRemovesCurrentChanges) { TEST_F(RemoteEventTest, DocumentUpdate) { std::unordered_map target_map = ActiveQueries({1}); - Document doc1 = Doc("docs/1", 1, Map("value", 1)); + MutableDocument doc1 = Doc("docs/1", 1, Map("value", 1)); auto change1 = MakeDocChange({1}, {}, doc1.key(), doc1); - Document doc2 = Doc("docs/2", 2, Map("value", 2)); + MutableDocument doc2 = Doc("docs/2", 2, Map("value", 2)); auto change2 = MakeDocChange({1}, {}, doc2.key(), doc2); WatchChangeAggregator aggregator = @@ -621,15 +617,15 @@ TEST_F(RemoteEventTest, DocumentUpdate) { target_metadata_provider_.SetSyncedKeys( DocumentKeySet{doc1.key(), doc2.key()}, target_map[1]); - NoDocument deleted_doc1 = DeletedDoc(doc1.key(), 3); + MutableDocument deleted_doc1 = DeletedDoc(doc1.key(), 3); DocumentWatchChange change3{{}, {1}, deleted_doc1.key(), deleted_doc1}; aggregator.HandleDocumentChange(change3); - Document updated_doc2 = Doc("docs/2", 3, Map("value", 2)); + MutableDocument updated_doc2 = Doc("docs/2", 3, Map("value", 2)); DocumentWatchChange change4{{1}, {}, updated_doc2.key(), updated_doc2}; aggregator.HandleDocumentChange(change4); - Document doc3 = Doc("docs/3", 3, Map("value", 3)); + MutableDocument doc3 = Doc("docs/3", 3, Map("value", 3)); DocumentWatchChange change5{{1}, {}, doc3.key(), doc3}; aggregator.HandleDocumentChange(change5); @@ -722,8 +718,8 @@ TEST_F(RemoteEventTest, SynthesizeDeletes) { 3, target_map, no_outstanding_responses_, DocumentKeySet{}, Changes(std::move(resolve_limbo_target))); - NoDocument expected(limbo_key, event.snapshot_version(), - /* has_committed_mutations= */ false); + MutableDocument expected = + MutableDocument::NoDocument(limbo_key, event.snapshot_version()); ASSERT_EQ(event.document_updates().at(limbo_key), expected); ASSERT_TRUE(event.limbo_document_changes().contains(limbo_key)); } @@ -757,18 +753,18 @@ TEST_F(RemoteEventTest, DoesntSynthesizeDeletesForExistingDoc) { TEST_F(RemoteEventTest, SeparatesDocumentUpdates) { std::unordered_map target_map = ActiveLimboQueries({1}); - Document new_doc = Doc("docs/new", 1, Map("key", "value")); + MutableDocument new_doc = Doc("docs/new", 1, Map("key", "value")); auto new_doc_change = MakeDocChange({1}, {}, new_doc.key(), new_doc); - Document existing_doc = Doc("docs/existing", 1, Map("some", "data")); + MutableDocument existing_doc = Doc("docs/existing", 1, Map("some", "data")); auto existing_doc_change = MakeDocChange({1}, {}, existing_doc.key(), existing_doc); - NoDocument deleted_doc = DeletedDoc("docs/deleted", 1); + MutableDocument deleted_doc = DeletedDoc("docs/deleted", 1); auto deleted_doc_change = MakeDocChange({}, {1}, deleted_doc.key(), deleted_doc); - NoDocument missing_doc = DeletedDoc("docs/missing", 1); + MutableDocument missing_doc = DeletedDoc("docs/missing", 1); auto missing_doc_change = MakeDocChange({}, {1}, missing_doc.key(), missing_doc); @@ -791,9 +787,9 @@ TEST_F(RemoteEventTest, TracksLimboDocuments) { target_map.insert(additional_targets.begin(), additional_targets.end()); // Add 3 docs: 1 is limbo and non-limbo, 2 is limbo-only, 3 is non-limbo - Document doc1 = Doc("docs/1", 1, Map("key", "value")); - Document doc2 = Doc("docs/2", 1, Map("key", "value")); - Document doc3 = Doc("docs/3", 1, Map("key", "value")); + MutableDocument doc1 = Doc("docs/1", 1, Map("key", "value")); + MutableDocument doc2 = Doc("docs/2", 1, Map("key", "value")); + MutableDocument doc3 = Doc("docs/3", 1, Map("key", "value")); // Target 2 is a limbo target auto doc_change1 = MakeDocChange({1, 2}, {}, doc1.key(), doc1); diff --git a/Firestore/core/test/unit/remote/serializer_test.cc b/Firestore/core/test/unit/remote/serializer_test.cc index abdec261b9d..dde10d9575b 100644 --- a/Firestore/core/test/unit/remote/serializer_test.cc +++ b/Firestore/core/test/unit/remote/serializer_test.cc @@ -38,20 +38,19 @@ #include "Firestore/Protos/cpp/google/firestore/v1/document.pb.h" #include "Firestore/Protos/cpp/google/firestore/v1/firestore.pb.h" #include "Firestore/core/include/firebase/firestore/firestore_errors.h" +#include "Firestore/core/include/firebase/firestore/geo_point.h" #include "Firestore/core/include/firebase/firestore/timestamp.h" #include "Firestore/core/src/core/bound.h" #include "Firestore/core/src/core/field_filter.h" #include "Firestore/core/src/core/query.h" #include "Firestore/core/src/local/target_data.h" #include "Firestore/core/src/model/delete_mutation.h" -#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/model/field_path.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/model/no_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/patch_mutation.h" #include "Firestore/core/src/model/set_mutation.h" #include "Firestore/core/src/model/snapshot_version.h" -#include "Firestore/core/src/model/unknown_document.h" +#include "Firestore/core/src/model/value_util.h" #include "Firestore/core/src/model/verify_mutation.h" #include "Firestore/core/src/nanopb/message.h" #include "Firestore/core/src/nanopb/reader.h" @@ -82,25 +81,27 @@ using local::TargetData; using model::ArrayTransform; using model::DatabaseId; using model::DeleteMutation; -using model::Document; using model::DocumentKey; using model::FieldPath; -using model::FieldValue; -using model::MaybeDocument; +using model::GetTypeOrder; +using model::MutableDocument; using model::Mutation; using model::MutationResult; -using model::NoDocument; using model::ObjectValue; using model::PatchMutation; using model::Precondition; +using model::RefValue; using model::ServerTimestampTransform; using model::SetMutation; using model::SnapshotVersion; +using model::SortFields; using model::TransformOperation; +using model::TypeOrder; using model::VerifyMutation; using nanopb::ByteString; using nanopb::ByteStringWriter; using nanopb::FreeNanopbMessage; +using nanopb::MakeSharedMessage; using nanopb::Message; using nanopb::ProtobufParse; using nanopb::ProtobufSerialize; @@ -172,16 +173,16 @@ class SerializerTest : public ::testing::Test { Serializer serializer; template - void ExpectRoundTrip(const Args&... args) { + void ExpectRoundTrip(Args&&... args) { // First, serialize model with our (nanopb based) serializer, then // deserialize the resulting bytes with libprotobuf and ensure the result is // the same as the expected proto. - ExpectSerializationRoundTrip(args...); + ExpectSerializationRoundTrip(std::forward(args)...); // Next, serialize proto with libprotobuf, then deserialize the resulting // bytes with our (nanopb based) deserializer and ensure the result is the // same as the expected model. - ExpectDeserializationRoundTrip(args...); + ExpectDeserializationRoundTrip(std::forward(args)...); } void ExpectNoDocumentDeserializationRoundTrip( @@ -226,7 +227,6 @@ class SerializerTest : public ::testing::Test { StringReader reader(bytes); auto message = Message::TryParse(&reader); - serializer.DecodeFieldValue(reader.context(), *message); ASSERT_NOT_OK(reader.status()); EXPECT_EQ(status.code(), reader.status().code()); @@ -245,11 +245,9 @@ class SerializerTest : public ::testing::Test { EXPECT_EQ(status.code(), reader.status().code()); } - ByteString EncodeFieldValue(const FieldValue& fv) { + ByteString EncodeFieldValue(const Message& fv) { ByteStringWriter writer; - google_firestore_v1_Value proto = serializer.EncodeFieldValue(fv); - writer.Write(google_firestore_v1_Value_fields, &proto); - FreeNanopbMessage(google_firestore_v1_Value_fields, &proto); + writer.Write(google_firestore_v1_Value_fields, fv.get()); return writer.Release(); } @@ -277,22 +275,22 @@ class SerializerTest : public ::testing::Test { } v1::Value ValueProto(std::nullptr_t) { - ByteString bytes = EncodeFieldValue(FieldValue::Null()); + ByteString bytes = EncodeFieldValue(Value(nullptr)); return ProtobufParse(bytes); } v1::Value ValueProto(bool b) { - ByteString bytes = EncodeFieldValue(FieldValue::FromBoolean(b)); + ByteString bytes = EncodeFieldValue(Value(b)); return ProtobufParse(bytes); } v1::Value ValueProto(int64_t i) { - ByteString bytes = EncodeFieldValue(FieldValue::FromInteger(i)); + ByteString bytes = EncodeFieldValue(Value(i)); return ProtobufParse(bytes); } v1::Value ValueProto(double d) { - ByteString bytes = EncodeFieldValue(FieldValue::FromDouble(d)); + ByteString bytes = EncodeFieldValue(Value(d)); return ProtobufParse(bytes); } @@ -307,38 +305,42 @@ class SerializerTest : public ::testing::Test { } v1::Value ValueProto(const std::string& s) { - ByteString bytes = EncodeFieldValue(FieldValue::FromString(s)); + ByteString bytes = EncodeFieldValue(Value(s)); return ProtobufParse(bytes); } v1::Value ValueProto(const Timestamp& ts) { - ByteString bytes = EncodeFieldValue(FieldValue::FromTimestamp(ts)); + ByteString bytes = EncodeFieldValue(Value(ts)); return ProtobufParse(bytes); } v1::Value ValueProto(const ByteString& blob) { - ByteString bytes = EncodeFieldValue(FieldValue::FromBlob(blob)); + ByteString bytes = EncodeFieldValue(Value(blob)); return ProtobufParse(bytes); } - v1::Value ValueProto(const FieldValue::Reference& ref) { - ByteString bytes = EncodeFieldValue( - FieldValue::FromReference(ref.database_id(), ref.key())); + v1::Value ValueProto(const DatabaseId& database_id, + const DocumentKey& document_key) { + ByteString bytes = EncodeFieldValue(RefValue(database_id, document_key)); return ProtobufParse(bytes); } v1::Value ValueProto(const GeoPoint& geo_point) { - ByteString bytes = EncodeFieldValue(FieldValue::FromGeoPoint(geo_point)); + ByteString bytes = EncodeFieldValue(Value(geo_point)); return ProtobufParse(bytes); } - v1::Value ValueProto(const std::vector& array) { - ByteString bytes = EncodeFieldValue(FieldValue::FromArray(array)); + v1::Value ValueProto(const Message& value) { + ByteString bytes = EncodeFieldValue(value); return ProtobufParse(bytes); } - v1::Value ValueProto(const FieldValue::Map& map) { - ByteString bytes = EncodeFieldValue(FieldValue::FromMap(map)); + v1::Value ValueProto(const Message& value) { + Message message; + message->which_value_type = google_firestore_v1_Value_array_value_tag; + message->array_value = *value; + ByteString bytes = EncodeFieldValue(message); + message.release(); return ProtobufParse(bytes); } @@ -367,9 +369,10 @@ class SerializerTest : public ::testing::Test { } void ExpectUnaryOperator(std::string op_str, - const FieldValue& value, + Message value, v1::StructuredQuery::UnaryFilter::Operator op) { - core::Query q = Query("docs").AddingFilter(Filter("prop", op_str, value)); + core::Query q = + Query("docs").AddingFilter(Filter("prop", op_str, std::move(value))); TargetData model = CreateTargetData(std::move(q)); v1::Target proto; @@ -408,29 +411,33 @@ class SerializerTest : public ::testing::Test { } private: - void ExpectSerializationRoundTrip(const FieldValue& model, - const v1::Value& proto, - FieldValue::Type type) { - EXPECT_EQ(type, model.type()); - ByteString bytes = EncodeFieldValue(model); + void ExpectSerializationRoundTrip( + const Message& model, + const v1::Value& proto, + TypeOrder type) { + EXPECT_EQ(type, GetTypeOrder(*model)); + ByteString bytes = EncodeFieldValue(std::move(model)); auto actual_proto = ProtobufParse(bytes); EXPECT_TRUE(msg_diff.Compare(proto, actual_proto)) << message_differences; } - void ExpectDeserializationRoundTrip(const FieldValue& model, - const v1::Value& proto, - FieldValue::Type type) { + void ExpectDeserializationRoundTrip( + const Message& model, + const v1::Value& proto, + TypeOrder type) { ByteString bytes = ProtobufSerialize(proto); StringReader reader(bytes); auto message = Message::TryParse(&reader); - FieldValue actual_model = - serializer.DecodeFieldValue(reader.context(), *message); - EXPECT_OK(reader.status()); - EXPECT_EQ(type, actual_model.type()); - EXPECT_EQ(model, actual_model); + EXPECT_EQ(type, GetTypeOrder(*message)); + // libprotobuf does not retain map ordering. We need to restore the + // ordering. + Message expected = model::DeepClone(*model); + SortFields(*expected); + SortFields(*message); + EXPECT_EQ(*expected, *message); } void ExpectSerializationRoundTrip( @@ -474,28 +481,24 @@ class SerializerTest : public ::testing::Test { auto message = Message::TryParse( &reader); - MaybeDocument actual_model = + + MutableDocument actual_model = serializer.DecodeMaybeDocument(reader.context(), *message); EXPECT_EQ(key, actual_model.key()); EXPECT_EQ(version, actual_model.version()); - switch (actual_model.type()) { - case MaybeDocument::Type::Document: { - Document actual_doc_model(actual_model); - EXPECT_EQ(value, actual_doc_model.data()); - break; - } - case MaybeDocument::Type::NoDocument: - EXPECT_FALSE(value.has_value()); - break; - case MaybeDocument::Type::UnknownDocument: - // TODO(rsgowman): implement. - // In particular, since this statement isn't hit, it implies a missing - // test for UnknownDocument. However, we'll defer that until after - // nanopb-master is merged to master. - abort(); - case MaybeDocument::Type::Invalid: - FAIL() << "We somehow created an invalid model object"; + if (actual_model.is_found_document()) { + EXPECT_EQ(value, actual_model.data()); + } else if (actual_model.is_no_document()) { + EXPECT_EQ(ObjectValue{}, actual_model.data()); + } else if (actual_model.is_unknown_document()) { + // TODO(rsgowman): implement. + // In particular, since this statement isn't hit, it implies a missing + // test for UnknownDocument. However, we'll defer that until after + // nanopb-master is merged to master. + abort(); + } else { + FAIL() << "We somehow created an invalid model object"; } } @@ -584,14 +587,14 @@ class SerializerTest : public ::testing::Test { }; TEST_F(SerializerTest, EncodesNull) { - FieldValue model = FieldValue::Null(); - ExpectRoundTrip(model, ValueProto(nullptr), FieldValue::Type::Null); + Message model = Value(nullptr); + ExpectRoundTrip(model, ValueProto(nullptr), TypeOrder::kNull); } TEST_F(SerializerTest, EncodesBool) { for (bool bool_value : {true, false}) { - FieldValue model = FieldValue::FromBoolean(bool_value); - ExpectRoundTrip(model, ValueProto(bool_value), FieldValue::Type::Boolean); + Message model = Value(bool_value); + ExpectRoundTrip(model, ValueProto(bool_value), TypeOrder::kBoolean); } } @@ -605,8 +608,8 @@ TEST_F(SerializerTest, EncodesIntegers) { std::numeric_limits::max()}; for (int64_t int_value : cases) { - FieldValue model = FieldValue::FromInteger(int_value); - ExpectRoundTrip(model, ValueProto(int_value), FieldValue::Type::Integer); + Message model = Value(int_value); + ExpectRoundTrip(model, ValueProto(int_value), TypeOrder::kNumber); } } @@ -644,8 +647,8 @@ TEST_F(SerializerTest, EncodesDoubles) { }; for (double double_value : cases) { - FieldValue model = FieldValue::FromDouble(double_value); - ExpectRoundTrip(model, ValueProto(double_value), FieldValue::Type::Double); + Message model = Value(double_value); + ExpectRoundTrip(model, ValueProto(double_value), TypeOrder::kNumber); } } @@ -667,8 +670,8 @@ TEST_F(SerializerTest, EncodesString) { }; for (const std::string& string_value : cases) { - FieldValue model = FieldValue::FromString(string_value); - ExpectRoundTrip(model, ValueProto(string_value), FieldValue::Type::String); + Message model = Value(string_value); + ExpectRoundTrip(model, ValueProto(string_value), TypeOrder::kString); } } @@ -684,8 +687,8 @@ TEST_F(SerializerTest, EncodesTimestamps) { }; for (const Timestamp& ts_value : cases) { - FieldValue model = FieldValue::FromTimestamp(ts_value); - ExpectRoundTrip(model, ValueProto(ts_value), FieldValue::Type::Timestamp); + Message model = Value(ts_value); + ExpectRoundTrip(model, ValueProto(ts_value), TypeOrder::kTimestamp); } } @@ -697,28 +700,26 @@ TEST_F(SerializerTest, EncodesBlobs) { }; for (const ByteString& blob_value : cases) { - FieldValue model = FieldValue::FromBlob(blob_value); - ExpectRoundTrip(model, ValueProto(blob_value), FieldValue::Type::Blob); + Message model = Value(blob_value); + ExpectRoundTrip(model, ValueProto(blob_value), TypeOrder::kBlob); } } TEST_F(SerializerTest, EncodesNullBlobs) { ByteString blob; ASSERT_EQ(blob.get(), nullptr); // Empty blobs are backed by a null buffer. - FieldValue model = FieldValue::FromBlob(blob); + Message model = Value(blob); // Avoid calling SerializerTest::EncodeFieldValue here because the Serializer // could be allocating an empty byte array. These assertions show that the // null blob really does materialize in the proto as null. - google_firestore_v1_Value proto = serializer.EncodeFieldValue(model); - ASSERT_EQ(proto.which_value_type, google_firestore_v1_Value_bytes_value_tag); - ASSERT_EQ(proto.bytes_value, nullptr); + ASSERT_EQ(model->which_value_type, google_firestore_v1_Value_bytes_value_tag); + ASSERT_EQ(model->bytes_value, nullptr); // Encoding a Value message containing a blob_value of null bytes results // in a non-empty message. ByteStringWriter writer; - writer.Write(google_firestore_v1_Value_fields, &proto); - FreeNanopbMessage(google_firestore_v1_Value_fields, &proto); + writer.Write(google_firestore_v1_Value_fields, model.get()); ByteString bytes = writer.Release(); ASSERT_GT(bytes.size(), 0); @@ -730,16 +731,10 @@ TEST_F(SerializerTest, EncodesNullBlobs) { } TEST_F(SerializerTest, EncodesReferences) { - std::vector cases{ - {DatabaseId{kProjectId, kDatabaseId}, - DocumentKey::FromPathString("baz/a")}, - }; - - for (const auto& ref_value : cases) { - FieldValue model = - FieldValue::FromReference(ref_value.database_id(), ref_value.key()); - ExpectRoundTrip(model, ValueProto(ref_value), FieldValue::Type::Reference); - } + Message ref_value = + RefValue(DatabaseId{kProjectId, kDatabaseId}, + DocumentKey::FromPathString("baz/a")); + ExpectRoundTrip(ref_value, ValueProto(ref_value), TypeOrder::kReference); } TEST_F(SerializerTest, EncodesGeoPoint) { @@ -748,64 +743,46 @@ TEST_F(SerializerTest, EncodesGeoPoint) { }; for (const GeoPoint& geo_value : cases) { - FieldValue model = FieldValue::FromGeoPoint(geo_value); - ExpectRoundTrip(model, ValueProto(geo_value), FieldValue::Type::GeoPoint); + Message model = Value(geo_value); + ExpectRoundTrip(model, ValueProto(geo_value), TypeOrder::kGeoPoint); } } TEST_F(SerializerTest, EncodesArray) { - std::vector> cases{ - // Empty Array. - {}, - // Typical Array. - {FieldValue::FromBoolean(true), FieldValue::FromString("foo")}, - // Nested Array. NB: the protos explicitly state that directly nested - // arrays are not allowed, however arrays *can* contain a map which - // contains another array. - {FieldValue::FromString("foo"), - FieldValue::FromMap( - {{"nested array", - FieldValue::FromArray( - {FieldValue::FromString("nested array value 1"), - FieldValue::FromString("nested array value 2")})}}), - FieldValue::FromString("bar")}}; - - for (const std::vector& array_value : cases) { - FieldValue model = FieldValue::FromArray(array_value); - ExpectRoundTrip(model, ValueProto(array_value), FieldValue::Type::Array); + std::vector> cases; + + // Empty Array. + cases.push_back(Array()); + // Typical Array. + cases.push_back(Array(true, "foo")); + // Nested Array. NB: the protos explicitly state that directly nested + // arrays are not allowed, however arrays *can* contain a map which + // contains another array. + cases.push_back(Array("foo", + Map("nested array", Array("nested array value 1", + "nested array value 2")), + "bar")); + + for (Message& array_value : cases) { + Message model = Value(std::move(array_value)); + ExpectRoundTrip(model, ValueProto(model), TypeOrder::kArray); } } TEST_F(SerializerTest, EncodesEmptyMap) { - FieldValue model = FieldValue::EmptyObject(); + Message model = Map(); v1::Value proto; proto.mutable_map_value(); - ExpectRoundTrip(model, proto, FieldValue::Type::Object); + ExpectRoundTrip(model, proto, TypeOrder::kMap); } TEST_F(SerializerTest, EncodesNestedObjects) { - FieldValue model = FieldValue::FromMap({ - {"b", FieldValue::True()}, - {"d", FieldValue::FromDouble(std::numeric_limits::max())}, - {"i", FieldValue::FromInteger(1)}, - {"n", FieldValue::Null()}, - {"s", FieldValue::FromString("foo")}, - {"a", FieldValue::FromArray( - {FieldValue::FromInteger(2), FieldValue::FromString("bar"), - FieldValue::FromMap({{"b", FieldValue::False()}})})}, - {"o", FieldValue::FromMap({ - {"d", FieldValue::FromInteger(100)}, - {"nested", FieldValue::FromMap({ - { - "e", - FieldValue::FromInteger( - std::numeric_limits::max()), - }, - })}, - })}, - }); + Message model = Map( + "b", true, "d", std::numeric_limits::max(), "i", 1, "n", nullptr, + "s", "foo", "a", Array(2, "bar", Map("b", false)), "o", + Map("d", 100, "nested", Map("e", std::numeric_limits::max()))); v1::Value inner_proto; google::protobuf::Map* inner_fields = @@ -838,7 +815,7 @@ TEST_F(SerializerTest, EncodesNestedObjects) { (*fields)["a"] = array_proto; (*fields)["o"] = middle_proto; - ExpectRoundTrip(model, proto, FieldValue::Type::Object); + ExpectRoundTrip(model, proto, TypeOrder::kMap); } TEST_F(SerializerTest, EncodesFieldValuesWithRepeatedEntries) { @@ -888,45 +865,31 @@ TEST_F(SerializerTest, EncodesFieldValuesWithRepeatedEntries) { // Decode the bytes into the model StringReader reader(bytes); - auto message = Message::TryParse(&reader); - FieldValue actual_model = - serializer.DecodeFieldValue(reader.context(), *message); + auto actual_model = Message::TryParse(&reader); EXPECT_OK(reader.status()); // Ensure the decoded model is as expected. - FieldValue expected_model = FieldValue::FromInteger(42); - EXPECT_EQ(FieldValue::Type::Integer, actual_model.type()); - EXPECT_EQ(expected_model, actual_model); -} - -TEST_F(SerializerTest, BadNullValue) { - std::vector bytes = MakeVector(EncodeFieldValue(FieldValue::Null())); - - // Alter the null value from 0 to 1. - Mutate(&bytes[1], /*expected_initial_value=*/0, /*new_value=*/1); - - ExpectFailedStatusDuringFieldValueDecode( - Status(Error::kErrorDataLoss, "ignored"), bytes); + Message expected_model = Value(42); + EXPECT_EQ(TypeOrder::kNumber, GetTypeOrder(*actual_model)); + EXPECT_EQ(*expected_model, *actual_model); } TEST_F(SerializerTest, BadBoolValueInterpretedAsTrue) { - std::vector bytes = - MakeVector(EncodeFieldValue(FieldValue::FromBoolean(true))); + std::vector bytes = MakeVector(EncodeFieldValue(Value(true))); // Alter the bool value from 1 to 2. (Value values are 0,1) Mutate(&bytes[1], /*expected_initial_value=*/1, /*new_value=*/2); StringReader reader(bytes); - auto message = Message::TryParse(&reader); - FieldValue model = serializer.DecodeFieldValue(reader.context(), *message); + auto actual_model = Message::TryParse(&reader); ASSERT_OK(reader.status()); - EXPECT_TRUE(model.boolean_value()); + EXPECT_TRUE(actual_model->boolean_value); } TEST_F(SerializerTest, BadIntegerValue) { // Encode 'maxint'. This should result in 9 0xff bytes, followed by a 1. - auto max_int = FieldValue::FromInteger(std::numeric_limits::max()); + auto max_int = Value(std::numeric_limits::max()); std::vector bytes = MakeVector(EncodeFieldValue(max_int)); ASSERT_EQ(11u, bytes.size()); for (size_t i = 1; i < bytes.size() - 1; i++) { @@ -943,8 +906,7 @@ TEST_F(SerializerTest, BadIntegerValue) { } TEST_F(SerializerTest, BadStringValue) { - std::vector bytes = - MakeVector(EncodeFieldValue(FieldValue::FromString("a"))); + std::vector bytes = MakeVector(EncodeFieldValue(Value("a"))); // Claim that the string length is 5 instead of 1. (The first two bytes are // used by the encoded tag.) @@ -954,35 +916,13 @@ TEST_F(SerializerTest, BadStringValue) { Status(Error::kErrorDataLoss, "ignored"), bytes); } -TEST_F(SerializerTest, BadTimestampValue_TooLarge) { - auto max_ts = FieldValue::FromTimestamp(TimestampInternal::Max()); - std::vector bytes = MakeVector(EncodeFieldValue(max_ts)); - - // Add some time, which should push us above the maximum allowed timestamp. - Mutate(&bytes[4], 0x82, 0x83); - - ExpectFailedStatusDuringFieldValueDecode( - Status(Error::kErrorDataLoss, "ignored"), bytes); -} - -TEST_F(SerializerTest, BadTimestampValue_TooSmall) { - auto min_ts = FieldValue::FromTimestamp(TimestampInternal::Min()); - std::vector bytes = MakeVector(EncodeFieldValue(min_ts)); - - // Remove some time, which should push us below the minimum allowed timestamp. - Mutate(&bytes[4], 0x92, 0x91); - - ExpectFailedStatusDuringFieldValueDecode( - Status(Error::kErrorDataLoss, "ignored"), bytes); -} - TEST_F(SerializerTest, BadFieldValueTagAndNoOtherTagPresent) { // A bad tag should be ignored. But if there are *no* valid tags, then we // don't know the type of the FieldValue. Although it might be reasonable to // assume some sort of default type in this situation, we've decided to fail // the deserialization process in this case instead. - std::vector bytes = MakeVector(EncodeFieldValue(FieldValue::Null())); + std::vector bytes = MakeVector(EncodeFieldValue(Value(nullptr))); // The v1::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 @@ -1031,19 +971,17 @@ TEST_F(SerializerTest, BadFieldValueTagWithOtherValidTagsPresent) { // Decode the bytes into the model StringReader reader(bytes); - auto message = Message::TryParse(&reader); - FieldValue actual_model = - serializer.DecodeFieldValue(reader.context(), *message); + auto actual_model = Message::TryParse(&reader); EXPECT_OK(reader.status()); // Ensure the decoded model is as expected. - FieldValue expected_model = FieldValue::FromBoolean(true); - EXPECT_EQ(FieldValue::Type::Boolean, actual_model.type()); - EXPECT_EQ(expected_model, actual_model); + Message expected_model = Value(true); + EXPECT_EQ(TypeOrder::kBoolean, GetTypeOrder(*actual_model)); + EXPECT_EQ(*expected_model, *actual_model); } TEST_F(SerializerTest, IncompleteFieldValue) { - std::vector bytes = MakeVector(EncodeFieldValue(FieldValue::Null())); + std::vector bytes = MakeVector(EncodeFieldValue(Value(nullptr))); ASSERT_EQ(2u, bytes.size()); // Remove the (null) payload @@ -1054,21 +992,6 @@ TEST_F(SerializerTest, IncompleteFieldValue) { Status(Error::kErrorDataLoss, "ignored"), bytes); } -TEST_F(SerializerTest, IncompleteTag) { - std::vector bytes; - ExpectFailedStatusDuringFieldValueDecode( - Status(Error::kErrorDataLoss, "ignored"), bytes); -} - -TEST_F(SerializerTest, FailOnInvalidInputBytes) { - // Invalid inputs should fail gracefully without assertions. The following - // bytes correspond to a Map FieldValue with an empty value. It was - // generated by our fuzz tests and used to trigger an assertion. - std::vector bytes = {0x32, 0x02, 0x0a, 0x00}; - ExpectFailedStatusDuringFieldValueDecode( - Status(Error::kErrorDataLoss, "ignored"), bytes); -} - TEST_F(SerializerTest, EncodesKey) { EXPECT_EQ(ResourceName(""), FromBytes(serializer.EncodeKey(Key("")))); EXPECT_EQ(ResourceName("one/two/three/four"), @@ -1113,7 +1036,7 @@ TEST_F(SerializerTest, BadKey) { TEST_F(SerializerTest, EncodesEmptyDocument) { DocumentKey key = DocumentKey::FromPathString("path/to/the/doc"); - ObjectValue empty_value = ObjectValue::Empty(); + ObjectValue empty_value{}; SnapshotVersion update_time = SnapshotVersion{{1234, 5678}}; v1::BatchGetDocumentsResponse proto; @@ -1133,13 +1056,8 @@ TEST_F(SerializerTest, EncodesEmptyDocument) { TEST_F(SerializerTest, EncodesNonEmptyDocument) { DocumentKey key = DocumentKey::FromPathString("path/to/the/doc"); - ObjectValue fields = ObjectValue::FromMap({ - {"foo", FieldValue::FromString("bar")}, - {"two", FieldValue::FromInteger(2)}, - {"nested", FieldValue::FromMap({ - {"fourty-two", FieldValue::FromInteger(42)}, - })}, - }); + ObjectValue fields{ + Map("foo", "bar", "two", 2, "nested", Map("fourty-two", 42))}; SnapshotVersion update_time = SnapshotVersion{{1234, 5678}}; v1::Value inner_proto; @@ -1400,11 +1318,11 @@ TEST_F(SerializerTest, EncodesSortOrders) { } TEST_F(SerializerTest, EncodesBounds) { - core::Query q = - Query("docs") - .StartingAt(Bound{{Value("prop"), Value(42)}, /*is_before=*/false}) - .EndingAt( - Bound{{Value("author"), Value("dimond")}, /*is_before=*/true}); + core::Query q = Query("docs") + .StartingAt(Bound::FromValue(Array("prop", 42), + /*is_before=*/false)) + .EndingAt(Bound::FromValue(Array("author", "dimond"), + /*is_before=*/true)); TargetData model = CreateTargetData(std::move(q)); v1::Target proto; @@ -1551,9 +1469,8 @@ TEST_F(SerializerTest, EncodesListenRequestLabels) { } TEST_F(SerializerTest, DecodesMutationResult) { - std::vector transformations({FieldValue::FromBoolean(true), - FieldValue::FromInteger(1234), - FieldValue::FromString("string")}); + Message transformations = + Array(true, 1234, "string"); auto version = Version(123456789); MutationResult model(version, std::move(transformations)); @@ -1742,53 +1659,6 @@ TEST_F(SerializerTest, DecodesVersionWithTargets) { ExpectDeserializationRoundTrip(model, proto); } -TEST_F(SerializerTest, GracefullyDecodesBadMapValue) { - pb_bytes_array_t key{1, {'a'}}; - google_firestore_v1_Value bad_value = {}; - google_firestore_v1_MapValue_FieldsEntry fields_entry = {&key, bad_value}; - google_firestore_v1_MapValue bad_map = {1, &fields_entry}; - - google_firestore_v1_Value bad_map_value = {}; - bad_map_value.which_value_type = google_firestore_v1_Value_map_value_tag; - bad_map_value.map_value = bad_map; - - StringReader reader(nullptr, 0); - FieldValue decoded_value = - serializer.DecodeFieldValue(reader.context(), bad_map_value); - EXPECT_FALSE(reader.context()->status().ok()); - EXPECT_TRUE(decoded_value.is_object()); - EXPECT_TRUE(decoded_value.object_value().empty()); -} - -TEST_F(SerializerTest, GracefullyDecodesBadReference) { - pb_bytes_array_t bad_ref{1, {'a'}}; - google_firestore_v1_Value bad_value = {}; - bad_value.which_value_type = google_firestore_v1_Value_reference_value_tag; - bad_value.reference_value = &bad_ref; - - StringReader reader(nullptr, 0); - FieldValue decoded_value = - serializer.DecodeFieldValue(reader.context(), bad_value); - EXPECT_FALSE(reader.context()->status().ok()); - EXPECT_TRUE(decoded_value.is_null()); -} - -TEST_F(SerializerTest, GracefullyDecodesArrayWithBadElement) { - google_firestore_v1_Value bad_element = {}; - google_firestore_v1_ArrayValue bad_array = {1, &bad_element}; - - google_firestore_v1_Value bad_value = {}; - bad_value.which_value_type = google_firestore_v1_Value_array_value_tag; - bad_value.array_value = bad_array; - - StringReader reader(nullptr, 0); - FieldValue decoded_value = - serializer.DecodeFieldValue(reader.context(), bad_value); - EXPECT_FALSE(reader.context()->status().ok()); - EXPECT_TRUE(decoded_value.is_array()); - EXPECT_TRUE(decoded_value.array_value().empty()); -} - TEST_F(SerializerTest, EncodesSetMutation) { SetMutation model = testutil::SetMutation("docs/1", Map("a", "b", "num", 1)); @@ -1813,8 +1683,7 @@ TEST_F(SerializerTest, EncodesPatchMutation) { auto& fields = *doc.mutable_fields(); fields["a"] = ValueProto("b"); fields["num"] = ValueProto(1); - auto nested = Map("thing'", Value(2)); - fields["some"] = ValueProto(Map("de\\ep", nested)); + fields["some"] = ValueProto(Map("de\\ep", Map("thing'", Value(2)))); v1::DocumentMask& mask = *proto.mutable_update_mask(); mask.add_field_paths("a"); @@ -1901,9 +1770,9 @@ TEST_F(SerializerTest, EncodesServerTimestampTransform) { TEST_F(SerializerTest, EncodesArrayTransform) { ArrayTransform array_union{TransformOperation::Type::ArrayUnion, - {Value("a"), Value(2)}}; + {Array("a", 2)}}; ArrayTransform array_remove{TransformOperation::Type::ArrayRemove, - {Value(Map("x", 1))}}; + {Array(Map("x", 1))}}; SetMutation set_model = testutil::SetMutation( "docs/1", Map(), {{"a", array_union}, {"bar", array_remove}}); @@ -2043,7 +1912,7 @@ TEST_F(SerializerTest, EncodesArrayContainsAnyFilter) { v1::StructuredQuery::FieldFilter& field = *proto.mutable_field_filter(); field.mutable_field()->set_field_path("item.tags"); field.set_op(v1::StructuredQuery::FieldFilter::ARRAY_CONTAINS_ANY); - *field.mutable_value() = ValueProto(std::vector{Value("food")}); + *field.mutable_value() = ValueProto(Array("food")); ExpectRoundTrip(model, proto); } @@ -2055,7 +1924,7 @@ TEST_F(SerializerTest, EncodesInFilter) { v1::StructuredQuery::FieldFilter& field = *proto.mutable_field_filter(); field.mutable_field()->set_field_path("item.tags"); field.set_op(v1::StructuredQuery::FieldFilter::IN_); - *field.mutable_value() = ValueProto(std::vector{Value("food")}); + *field.mutable_value() = ValueProto(Array("food")); ExpectRoundTrip(model, proto); } @@ -2067,21 +1936,19 @@ TEST_F(SerializerTest, EncodesNotInFilter) { v1::StructuredQuery::FieldFilter& field = *proto.mutable_field_filter(); field.mutable_field()->set_field_path("item.tags"); field.set_op(v1::StructuredQuery::FieldFilter::NOT_IN); - *field.mutable_value() = ValueProto(std::vector{Value("food")}); + *field.mutable_value() = ValueProto(Array("food")); ExpectRoundTrip(model, proto); } TEST_F(SerializerTest, EncodesNotInFilterWithNull) { - auto model = - testutil::Filter("item.tags", "not-in", Array(FieldValue::Null())); + auto model = testutil::Filter("item.tags", "not-in", Array(nullptr)); v1::StructuredQuery::Filter proto; v1::StructuredQuery::FieldFilter& field = *proto.mutable_field_filter(); field.mutable_field()->set_field_path("item.tags"); field.set_op(v1::StructuredQuery::FieldFilter::NOT_IN); - *field.mutable_value() = - ValueProto(std::vector{FieldValue::Null()}); + *field.mutable_value() = ValueProto(Array(nullptr)); ExpectRoundTrip(model, proto); } @@ -2093,8 +1960,7 @@ TEST_F(SerializerTest, EncodesKeyFieldFilter) { v1::StructuredQuery::FieldFilter& field = *proto.mutable_field_filter(); field.mutable_field()->set_field_path("__name__"); field.set_op(v1::StructuredQuery::FieldFilter::EQUAL); - *field.mutable_value() = - ValueProto(FieldValue::Reference{DatabaseId{"p", "d"}, Key("coll/doc")}); + *field.mutable_value() = ValueProto(DatabaseId{"p", "d"}, Key("coll/doc")); ExpectRoundTrip(model, proto); } diff --git a/Firestore/core/test/unit/remote/watch_change_test.cc b/Firestore/core/test/unit/remote/watch_change_test.cc index d3c84db188d..67a7e7d3a6f 100644 --- a/Firestore/core/test/unit/remote/watch_change_test.cc +++ b/Firestore/core/test/unit/remote/watch_change_test.cc @@ -16,7 +16,7 @@ #include "Firestore/core/src/remote/watch_change.h" -#include "Firestore/core/src/model/document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/remote/existence_filter.h" #include "Firestore/core/test/unit/testutil/testutil.h" #include "gtest/gtest.h" @@ -25,14 +25,13 @@ namespace firebase { namespace firestore { namespace remote { -using model::DocumentState; -using model::MaybeDocument; +using model::MutableDocument; using testutil::Doc; using testutil::Map; TEST(WatchChangeTest, CanCreateDocumentWatchChange) { - MaybeDocument doc = Doc("a/b", 1, Map()); + MutableDocument doc = Doc("a/b", 1, Map()); DocumentWatchChange change{{1, 2, 3}, {4, 5}, doc.key(), doc}; EXPECT_EQ(change.updated_target_ids().size(), 3); diff --git a/Firestore/core/test/unit/testutil/testutil.cc b/Firestore/core/test/unit/testutil/testutil.cc index c374b4d6450..df8bdf97380 100644 --- a/Firestore/core/test/unit/testutil/testutil.cc +++ b/Firestore/core/test/unit/testutil/testutil.cc @@ -28,39 +28,48 @@ #include "Firestore/core/src/core/query.h" #include "Firestore/core/src/model/delete_mutation.h" #include "Firestore/core/src/model/document.h" +#include "Firestore/core/src/model/document_key.h" #include "Firestore/core/src/model/document_set.h" #include "Firestore/core/src/model/field_mask.h" #include "Firestore/core/src/model/field_path.h" #include "Firestore/core/src/model/field_transform.h" -#include "Firestore/core/src/model/field_value.h" -#include "Firestore/core/src/model/no_document.h" +#include "Firestore/core/src/model/mutable_document.h" #include "Firestore/core/src/model/patch_mutation.h" #include "Firestore/core/src/model/precondition.h" #include "Firestore/core/src/model/set_mutation.h" #include "Firestore/core/src/model/transform_operation.h" -#include "Firestore/core/src/model/unknown_document.h" +#include "Firestore/core/src/model/value_util.h" #include "Firestore/core/src/model/verify_mutation.h" #include "Firestore/core/src/nanopb/byte_string.h" +#include "Firestore/core/src/nanopb/message.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" #include "Firestore/core/src/util/hard_assert.h" #include "Firestore/core/src/util/statusor.h" +#include "Firestore/core/src/util/string_format.h" #include "absl/memory/memory.h" namespace firebase { namespace firestore { namespace testutil { +using model::DeepClone; using model::Document; using model::DocumentComparator; +using model::DocumentKey; using model::DocumentSet; -using model::DocumentState; using model::FieldMask; using model::FieldPath; using model::FieldTransform; -using model::FieldValue; +using model::MutableDocument; +using model::NullValue; using model::ObjectValue; using model::Precondition; using model::TransformOperation; using nanopb::ByteString; +using nanopb::Message; +using nanopb::SetRepeatedField; +using nanopb::SharedMessage; +using util::StringFormat; /** * A string sentinel that can be used with PatchMutation() to mark a field for @@ -68,11 +77,28 @@ using nanopb::ByteString; */ constexpr const char* kDeleteSentinel = ""; +// We use a canonical NaN bit pattern that's common for both Objective-C and +// Java. Specifically: +// +// - sign: 0 +// - exponent: 11 bits, all 1 +// - significand: 52 bits, MSB=1, rest=0 +// +// This matches the Firestore backend which uses Double.doubleToLongBits from +// the JDK (which is defined to normalize all NaNs to this value). This also +// happens to be a common value for NAN in C++, but C++ does not require this +// specific NaN value to be used, so we normalize. +const uint64_t kCanonicalNanBits = 0x7ff8000000000000ULL; + namespace details { -FieldValue BlobValue(std::initializer_list octets) { +Message BlobValue( + std::initializer_list octets) { nanopb::ByteString contents{octets}; - return FieldValue::FromBlob(std::move(contents)); + Message result; + result->which_value_type = google_firestore_v1_Value_bytes_value_tag; + result->bytes_value = nanopb::MakeBytesArray(octets.begin(), octets.size()); + return result; } } // namespace details @@ -81,44 +107,81 @@ ByteString Bytes(std::initializer_list octets) { return ByteString(octets); } -FieldValue Value(std::nullptr_t) { - return FieldValue::Null(); +Message Value(std::nullptr_t) { + return NullValue(); +} + +Message Value(double value) { + Message result; + result->which_value_type = google_firestore_v1_Value_double_value_tag; + result->double_value = value; + return result; } -FieldValue Value(double value) { - return FieldValue::FromDouble(value); +Message Value(Timestamp value) { + Message result; + result->which_value_type = google_firestore_v1_Value_timestamp_value_tag; + result->timestamp_value.seconds = value.seconds(); + result->timestamp_value.nanos = value.nanoseconds(); + return result; } -FieldValue Value(Timestamp value) { - return FieldValue::FromTimestamp(value); +Message Value(const char* value) { + Message result; + result->which_value_type = google_firestore_v1_Value_string_value_tag; + result->string_value = nanopb::MakeBytesArray(value); + return result; } -FieldValue Value(const char* value) { - return FieldValue::FromString(value); +Message Value(const std::string& value) { + Message result; + result->which_value_type = google_firestore_v1_Value_string_value_tag; + result->string_value = nanopb::MakeBytesArray(value); + return result; } -FieldValue Value(const std::string& value) { - return FieldValue::FromString(value); +Message Value(const nanopb::ByteString& value) { + Message result; + result->which_value_type = google_firestore_v1_Value_bytes_value_tag; + result->bytes_value = nanopb::MakeBytesArray(value.begin(), value.size()); + return result; } -FieldValue Value(const GeoPoint& value) { - return FieldValue::FromGeoPoint(value); +Message Value(const GeoPoint& value) { + Message result; + result->which_value_type = google_firestore_v1_Value_geo_point_value_tag; + result->geo_point_value.latitude = value.latitude(); + result->geo_point_value.longitude = value.longitude(); + return result; } -FieldValue Value(const FieldValue& value) { +Message Value( + Message value) { return value; } -FieldValue Value(const model::ObjectValue& value) { - return value.AsFieldValue(); +Message Value( + Message value) { + Message result; + result->which_value_type = google_firestore_v1_Value_map_value_tag; + result->map_value = *value.release(); + return result; +} + +Message Value( + Message value) { + Message result; + result->which_value_type = google_firestore_v1_Value_array_value_tag; + result->array_value = *value.release(); + return result; } -FieldValue Value(const FieldValue::Map& value) { - return Value(model::ObjectValue::FromMap(value)); +Message Value(const model::ObjectValue& value) { + return DeepClone(value.Get()); } -model::ObjectValue WrapObject(const model::FieldValue::Map& value) { - return model::ObjectValue::FromMap(value); +ObjectValue WrapObject(Message value) { + return ObjectValue{std::move(value)}; } model::DocumentKey Key(absl::string_view path) { @@ -141,8 +204,15 @@ model::DatabaseId DbId(std::string project) { } } -FieldValue Ref(std::string project, absl::string_view path) { - return FieldValue::FromReference(DbId(std::move(project)), Key(path)); +Message Ref(std::string project, + absl::string_view path) { + model::DatabaseId database_id = DbId(std::move(project)); + Message result; + result->which_value_type = google_firestore_v1_Value_reference_value_tag; + result->string_value = nanopb::MakeBytesArray( + StringFormat("projects/%s/databases/%s/documents/%s", + database_id.project_id(), database_id.database_id(), path)); + return result; } model::ResourcePath Resource(absl::string_view field) { @@ -156,49 +226,32 @@ model::SnapshotVersion Version(int64_t version) { return model::SnapshotVersion{Timestamp::FromTimePoint(timepoint)}; } -model::Document Doc(absl::string_view key, - int64_t version, - const model::FieldValue::Map& data) { - return Doc(key, version, data, DocumentState::kSynced); +model::MutableDocument Doc(absl::string_view key, + int64_t version, + Message data) { + return MutableDocument::FoundDocument(Key(key), Version(version), + ObjectValue{std::move(data)}); } -model::Document Doc(absl::string_view key, - int64_t version, - const model::FieldValue::Map& data, - model::DocumentState document_state) { - return model::Document(model::ObjectValue::FromMap(data), Key(key), - Version(version), document_state); +model::MutableDocument Doc(absl::string_view key, int64_t version) { + return MutableDocument::FoundDocument(Key(key), Version(version), + ObjectValue{}); } -model::Document Doc(absl::string_view key, - int64_t version, - const FieldValue& data) { - return Doc(key, version, data, DocumentState::kSynced); +model::MutableDocument DeletedDoc(absl::string_view key, int64_t version) { + return MutableDocument::NoDocument(Key(key), Version(version)); } -model::Document Doc(absl::string_view key, - int64_t version, - const FieldValue& data, - model::DocumentState document_state) { - return model::Document(model::ObjectValue(data), Key(key), Version(version), - document_state); +model::MutableDocument DeletedDoc(DocumentKey key, int64_t version) { + return MutableDocument::NoDocument(std::move(key), Version(version)); } -model::NoDocument DeletedDoc(absl::string_view key, - int64_t version, - bool has_committed_mutations) { - return model::NoDocument(Key(key), Version(version), has_committed_mutations); +model::MutableDocument UnknownDoc(absl::string_view key, int64_t version) { + return MutableDocument::UnknownDocument(Key(key), Version(version)); } -model::NoDocument DeletedDoc(model::DocumentKey key, - int64_t version, - bool has_committed_mutations) { - return model::NoDocument(std::move(key), Version(version), - has_committed_mutations); -} - -model::UnknownDocument UnknownDoc(absl::string_view key, int64_t version) { - return model::UnknownDocument(Key(key), Version(version)); +model::MutableDocument InvalidDoc(absl::string_view key) { + return MutableDocument::InvalidDocument(Key(key)); } DocumentComparator DocComparator(absl::string_view field_path) { @@ -243,39 +296,40 @@ core::Filter::Operator OperatorFromString(absl::string_view s) { core::FieldFilter Filter(absl::string_view key, absl::string_view op, - FieldValue value) { + Message value) { return core::FieldFilter::Create(Field(key), OperatorFromString(op), std::move(value)); } core::FieldFilter Filter(absl::string_view key, absl::string_view op, - FieldValue::Map value) { - return Filter(key, op, FieldValue::FromMap(std::move(value))); + Message value) { + return core::FieldFilter::Create(Field(key), OperatorFromString(op), + Value(std::move(value))); } core::FieldFilter Filter(absl::string_view key, absl::string_view op, std::nullptr_t) { - return Filter(key, op, FieldValue::Null()); + return Filter(key, op, NullValue()); } core::FieldFilter Filter(absl::string_view key, absl::string_view op, const char* value) { - return Filter(key, op, FieldValue::FromString(value)); + return Filter(key, op, Value(value)); } core::FieldFilter Filter(absl::string_view key, absl::string_view op, int value) { - return Filter(key, op, FieldValue::FromInteger(value)); + return Filter(key, op, Value(value)); } core::FieldFilter Filter(absl::string_view key, absl::string_view op, double value) { - return Filter(key, op, FieldValue::FromDouble(value)); + return Filter(key, op, Value(value)); } core::Direction Direction(absl::string_view direction) { @@ -310,7 +364,7 @@ core::Query CollectionGroupQuery(absl::string_view collection_id) { // UserDataWriter changes are ported from Web and Android. model::SetMutation SetMutation( absl::string_view path, - const model::FieldValue::Map& values, + Message values, std::vector> transforms) { std::vector field_transforms; for (auto&& pair : transforms) { @@ -320,7 +374,7 @@ model::SetMutation SetMutation( field_transforms.push_back(std::move(transform)); } - return model::SetMutation(Key(path), model::ObjectValue::FromMap(values), + return model::SetMutation(Key(path), model::ObjectValue{std::move(values)}, model::Precondition::None(), std::move(field_transforms)); } @@ -330,10 +384,10 @@ model::SetMutation SetMutation( // UserDataWriter changes are ported from Web and Android. model::PatchMutation PatchMutation( absl::string_view path, - const FieldValue::Map& values, + Message values, // TODO(rsgowman): Investigate changing update_mask to a set. std::vector> transforms) { - return PatchMutationHelper(path, values, transforms, + return PatchMutationHelper(path, std::move(values), std::move(transforms), Precondition::Exists(true), absl::nullopt); } @@ -342,20 +396,20 @@ model::PatchMutation PatchMutation( // UserDataWriter changes are ported from Web and Android. model::PatchMutation MergeMutation( absl::string_view path, - const FieldValue::Map& values, + Message values, const std::vector& update_mask, std::vector> transforms) { - return PatchMutationHelper(path, values, transforms, Precondition::None(), - update_mask); + return PatchMutationHelper(path, std::move(values), std::move(transforms), + Precondition::None(), update_mask); } model::PatchMutation PatchMutationHelper( absl::string_view path, - const FieldValue::Map& values, + Message values, std::vector> transforms, Precondition precondition, const absl::optional>& update_mask) { - ObjectValue object_value = ObjectValue::Empty(); + ObjectValue object_value{}; std::set field_mask_paths; std::vector field_transforms; @@ -366,16 +420,16 @@ model::PatchMutation PatchMutationHelper( field_transforms.push_back(std::move(transform)); } - for (const auto& kv : values) { - FieldPath field_path = Field(kv.first); + for (pb_size_t i = 0; i < values->map_value.fields_count; ++i) { + FieldPath field_path = + Field(nanopb::MakeStringView(values->map_value.fields[i].key)); field_mask_paths.insert(field_path); - - const FieldValue& value = kv.second; - if (!value.is_string() || value.string_value() != kDeleteSentinel) { - object_value = object_value.Set(field_path, value); - } else if (value.string_value() == kDeleteSentinel) { - object_value = - object_value.Set(field_path, object_value.Delete(field_path)); + const google_firestore_v1_Value& value = values->map_value.fields[i].value; + if (value.which_value_type != google_firestore_v1_Value_string_value_tag || + nanopb::MakeStringView(value.string_value) != kDeleteSentinel) { + object_value.Set(field_path, DeepClone(value)); + } else if (nanopb::MakeStringView(value.string_value) == kDeleteSentinel) { + object_value.Delete(field_path); } } @@ -389,8 +443,8 @@ model::PatchMutation PatchMutationHelper( std::move(field_transforms)); } -std::pair Increment(std::string field, - FieldValue operand) { +std::pair Increment( + std::string field, Message operand) { model::NumericIncrementTransform transform(std::move(operand)); return std::pair(std::move(field), @@ -398,10 +452,16 @@ std::pair Increment(std::string field, } std::pair ArrayUnion( - std::string field, std::vector operands) { + std::string field, + const std::vector>& operands) { + Message array_value; + SetRepeatedField(&array_value->values, &array_value->values_count, + operands.begin(), operands.end(), + [](const Message& value) { + return *DeepClone(*value).release(); + }); model::ArrayTransform transform(TransformOperation::Type::ArrayUnion, - std::move(operands)); - + std::move(array_value)); return std::pair(std::move(field), std::move(transform)); } @@ -416,7 +476,7 @@ model::VerifyMutation VerifyMutation(absl::string_view path, int64_t version) { } model::MutationResult MutationResult(int64_t version) { - return model::MutationResult(Version(version), absl::nullopt); + return model::MutationResult(Version(version), Array()); } nanopb::ByteString ResumeToken(int64_t snapshot_version) { diff --git a/Firestore/core/test/unit/testutil/testutil.h b/Firestore/core/test/unit/testutil/testutil.h index 79645cd3d50..f1fa2b0e0c1 100644 --- a/Firestore/core/test/unit/testutil/testutil.h +++ b/Firestore/core/test/unit/testutil/testutil.h @@ -17,15 +17,23 @@ #ifndef FIRESTORE_CORE_TEST_UNIT_TESTUTIL_TESTUTIL_H_ #define FIRESTORE_CORE_TEST_UNIT_TESTUTIL_TESTUTIL_H_ -#include +#include #include #include #include #include +#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h" #include "Firestore/core/src/core/core_fwd.h" -#include "Firestore/core/src/model/field_value.h" +#include "Firestore/core/src/core/direction.h" +#include "Firestore/core/src/model/document_key.h" +#include "Firestore/core/src/model/document_set.h" #include "Firestore/core/src/model/model_fwd.h" +#include "Firestore/core/src/model/precondition.h" +#include "Firestore/core/src/model/value_util.h" +#include "Firestore/core/src/nanopb/byte_string.h" +#include "Firestore/core/src/nanopb/message.h" +#include "Firestore/core/src/nanopb/nanopb_util.h" #include "absl/strings/string_view.h" namespace firebase { @@ -41,15 +49,19 @@ class ByteString; namespace testutil { namespace details { -model::FieldValue BlobValue(std::initializer_list); +nanopb::Message BlobValue( + std::initializer_list); } // namespace details +// A bit pattern for our canonical NaN value. +ABSL_CONST_INIT extern const uint64_t kCanonicalNanBits; + // Convenience methods for creating instances for tests. nanopb::ByteString Bytes(std::initializer_list); -model::FieldValue Value(std::nullptr_t); +nanopb::Message Value(std::nullptr_t); /** * A type definition that evaluates to type V only if T is exactly type `bool`. @@ -75,8 +87,12 @@ using EnableForInts = typename std::enable_if::value && * @param bool_value A boolean value that disallows implicit conversions. */ template -EnableForExactlyBool Value(T bool_value) { - return model::FieldValue::FromBoolean(bool_value); +EnableForExactlyBool> Value( + T bool_value) { + nanopb::Message result; + result->which_value_type = google_firestore_v1_Value_boolean_value_tag; + result->boolean_value = bool_value; + return result; } /** @@ -91,39 +107,50 @@ EnableForExactlyBool Value(T bool_value) { * @param value An integer value. */ template -EnableForInts Value(T value) { - return model::FieldValue::FromInteger(value); +EnableForInts> Value(T value) { + nanopb::Message result; + result->which_value_type = google_firestore_v1_Value_integer_value_tag; + result->integer_value = value; + return result; } -model::FieldValue Value(double value); +nanopb::Message Value(double value); -model::FieldValue Value(Timestamp value); +nanopb::Message Value(Timestamp value); -model::FieldValue Value(const char* value); +nanopb::Message Value(const char* value); -model::FieldValue Value(const std::string& value); +nanopb::Message Value(const std::string& value); -model::FieldValue Value(const GeoPoint& value); +nanopb::Message Value( + const nanopb::ByteString& value); + +nanopb::Message Value(const GeoPoint& value); template -model::FieldValue BlobValue(Ints... octets) { +nanopb::Message BlobValue(Ints... octets) { return details::BlobValue({static_cast(octets)...}); } -// This overload allows Object() to appear as a value (along with any explicitly -// constructed FieldValues). -model::FieldValue Value(const model::FieldValue& value); +nanopb::Message Value( + nanopb::Message value); + +nanopb::Message Value( + nanopb::Message value); -model::FieldValue Value(const model::ObjectValue& value); +nanopb::Message Value( + nanopb::Message value); -model::FieldValue Value(const model::FieldValue::Map& value); +nanopb::Message Value( + const model::ObjectValue& value); namespace details { /** * Recursive base case for AddPairs, below. Returns the map. */ -inline model::FieldValue::Map AddPairs(const model::FieldValue::Map& prior) { +inline nanopb::Message AddPairs( + nanopb::Message prior) { return prior; } @@ -139,11 +166,22 @@ inline model::FieldValue::Map AddPairs(const model::FieldValue::Map& prior) { * @return The resulting map. */ template -model::FieldValue::Map AddPairs(const model::FieldValue::Map& prior, - const std::string& key, - const ValueType& value, - Args... rest) { - return AddPairs(prior.insert(key, Value(value)), rest...); +nanopb::Message AddPairs( + nanopb::Message prior, + const std::string& key, + ValueType value, + Args... rest) { + nanopb::Message result = std::move(prior); + result->which_value_type = google_firestore_v1_Value_map_value_tag; + size_t new_count = result->map_value.fields_count + 1; + result->map_value.fields_count = nanopb::CheckedSize(new_count); + result->map_value.fields = + nanopb::ResizeArray( + result->map_value.fields, new_count); + result->map_value.fields[new_count - 1].key = nanopb::MakeBytesArray(key); + result->map_value.fields[new_count - 1].value = + *Value(std::move(value)).release(); + return AddPairs(std::move(result), std::forward(rest)...); } /** @@ -153,20 +191,60 @@ model::FieldValue::Map AddPairs(const model::FieldValue::Map& prior, * be passed to Value(). */ template -model::FieldValue::Map MakeMap(Args... key_value_pairs) { - return AddPairs(model::FieldValue::Map(), key_value_pairs...); +nanopb::Message MakeMap(Args... key_value_pairs) { + nanopb::Message map_value; + map_value->which_value_type = google_firestore_v1_Value_map_value_tag; + map_value->map_value = {}; + return AddPairs(std::move(map_value), std::forward(key_value_pairs)...); +} + +/** + * Recursive base case for AddElements, below. + */ +inline void AddElements(nanopb::Message&, + pb_size_t) { +} + +/** + * Inserts the given element into the array, and then recursively calls + * AddElement to add any remaining arguments. + * + * @param array_value An array into which the values should be inserted. + * @param pos The index of the next element. + * @param value The element to insert. + * @param rest Any remaining arguments + */ +template +void AddElements(nanopb::Message& array_value, + pb_size_t pos, + ValueType value, + Args&&... rest) { + array_value->values[pos] = *Value(std::move(value)).release(); + AddElements(array_value, ++pos, std::forward(rest)...); +} + +/** + * Inserts the elements into the given array. + */ +template +nanopb::Message MakeArray(Args&&... values) { + nanopb::Message array_value; + array_value->values_count = nanopb::CheckedSize(sizeof...(Args)); + array_value->values = + nanopb::MakeArray(array_value->values_count); + AddElements(array_value, 0, std::forward(values)...); + return array_value; } } // namespace details template -model::FieldValue Array(Args... values) { - std::vector contents{Value(values)...}; - return model::FieldValue::FromArray(std::move(contents)); +nanopb::Message Array(Args&&... values) { + return details::MakeArray(std::move(values)...); } /** Wraps an immutable sorted map into an ObjectValue. */ -model::ObjectValue WrapObject(const model::FieldValue::Map& value); +model::ObjectValue WrapObject(nanopb::Message value); /** * Creates an ObjectValue from the given key/value pairs. @@ -176,7 +254,7 @@ model::ObjectValue WrapObject(const model::FieldValue::Map& value); */ template model::ObjectValue WrapObject(Args... key_value_pairs) { - return WrapObject(details::MakeMap(key_value_pairs...)); + return WrapObject(details::MakeMap(std::move(key_value_pairs)...)); } /** @@ -186,8 +264,8 @@ model::ObjectValue WrapObject(Args... key_value_pairs) { * be passed to Value(). */ template -model::FieldValue::Map Map(Args... key_value_pairs) { - return details::MakeMap(key_value_pairs...); +nanopb::Message Map(Args... key_value_pairs) { + return details::MakeMap(std::move(key_value_pairs)...); } model::DocumentKey Key(absl::string_view path); @@ -196,7 +274,8 @@ model::FieldPath Field(absl::string_view field); model::DatabaseId DbId(std::string project = "project/(default)"); -model::FieldValue Ref(std::string project, absl::string_view path); +nanopb::Message Ref(std::string project, + absl::string_view path); model::ResourcePath Resource(absl::string_view field); @@ -207,37 +286,23 @@ model::ResourcePath Resource(absl::string_view field); */ model::SnapshotVersion Version(int64_t version); -model::Document Doc( - absl::string_view key, - int64_t version = 0, - const model::FieldValue::Map& data = model::FieldValue::Map()); +model::MutableDocument Doc(absl::string_view key, int64_t version = 0); -model::Document Doc(absl::string_view key, - int64_t version, - const model::FieldValue::Map& data, - model::DocumentState document_state); - -model::Document Doc(absl::string_view key, - int64_t version, - const model::FieldValue& data); - -model::Document Doc(absl::string_view key, - int64_t version, - const model::FieldValue& data, - model::DocumentState document_state); +model::MutableDocument Doc(absl::string_view key, + int64_t version, + nanopb::Message data); /** A convenience method for creating deleted docs for tests. */ -model::NoDocument DeletedDoc(absl::string_view key, - int64_t version = 0, - bool has_committed_mutations = false); +model::MutableDocument DeletedDoc(absl::string_view key, int64_t version = 0); /** A convenience method for creating deleted docs for tests. */ -model::NoDocument DeletedDoc(model::DocumentKey key, - int64_t version = 0, - bool has_committed_mutations = false); +model::MutableDocument DeletedDoc(model::DocumentKey key, int64_t version = 0); /** A convenience method for creating unknown docs for tests. */ -model::UnknownDocument UnknownDoc(absl::string_view key, int64_t version); +model::MutableDocument UnknownDoc(absl::string_view key, int64_t version); + +/** A convenience method for creating invalid (missing) docs for tests. */ +model::MutableDocument InvalidDoc(absl::string_view key); /** * Creates a DocumentComparator that will compare Documents by the given @@ -254,11 +319,11 @@ model::DocumentSet DocSet(model::DocumentComparator comp, core::FieldFilter Filter(absl::string_view key, absl::string_view op, - model::FieldValue value); + nanopb::Message value); core::FieldFilter Filter(absl::string_view key, absl::string_view op, - model::FieldValue::Map value); + nanopb::Message value); core::FieldFilter Filter(absl::string_view key, absl::string_view op, @@ -296,26 +361,26 @@ core::Query CollectionGroupQuery(absl::string_view collection_id); model::SetMutation SetMutation( absl::string_view path, - const model::FieldValue::Map& values = model::FieldValue::Map(), + nanopb::Message values, std::vector> transforms = {}); model::PatchMutation PatchMutation( absl::string_view path, - const model::FieldValue::Map& values = model::FieldValue::Map(), + nanopb::Message values, std::vector> transforms = {}); model::PatchMutation MergeMutation( absl::string_view path, - const model::FieldValue::Map& values, + nanopb::Message values, const std::vector& update_mask, std::vector> transforms = {}); model::PatchMutation PatchMutationHelper( absl::string_view path, - const model::FieldValue::Map& values, + nanopb::Message values, std::vector> transforms, model::Precondition precondition, const absl::optional>& update_mask); @@ -326,7 +391,7 @@ model::PatchMutation PatchMutationHelper( * above. */ std::pair Increment( - std::string field, model::FieldValue operand); + std::string field, nanopb::Message operand); /** * Creates a pair of field name, TransformOperation that represents an array @@ -334,7 +399,8 @@ std::pair Increment( * above. */ std::pair ArrayUnion( - std::string field, std::vector operands); + std::string field, + const std::vector>& operands); model::DeleteMutation DeleteMutation(absl::string_view path); diff --git a/Firestore/core/test/unit/testutil/view_testing.cc b/Firestore/core/test/unit/testutil/view_testing.cc index 73e3211ef14..ad241700f37 100644 --- a/Firestore/core/test/unit/testutil/view_testing.cc +++ b/Firestore/core/test/unit/testutil/view_testing.cc @@ -20,6 +20,7 @@ #include "Firestore/core/src/core/view.h" #include "Firestore/core/src/core/view_snapshot.h" +#include "Firestore/core/src/model/document.h" #include "Firestore/core/src/remote/remote_event.h" namespace firebase { @@ -31,23 +32,21 @@ using core::ViewChange; using core::ViewSnapshot; using model::Document; using model::DocumentKeySet; -using model::MaybeDocument; -using model::MaybeDocumentMap; +using model::DocumentMap; using nanopb::ByteString; using remote::TargetChange; -model::MaybeDocumentMap DocUpdates( - const std::vector& docs) { - MaybeDocumentMap updates; - for (const MaybeDocument& doc : docs) { - updates = updates.insert(doc.key(), doc); +model::DocumentMap DocUpdates(const std::vector& docs) { + DocumentMap updates; + for (const Document& doc : docs) { + updates = updates.insert(doc->key(), doc); } return updates; } absl::optional ApplyChanges( View* view, - const std::vector& docs, + const std::vector& docs, const absl::optional& target_change) { ViewChange change = view->ApplyChanges( view->ComputeDocumentChanges(DocUpdates(docs)), target_change); @@ -65,7 +64,7 @@ TargetChange AckTarget(DocumentKeySet docs) { TargetChange AckTarget(std::initializer_list docs) { DocumentKeySet keys; for (const auto& doc : docs) { - keys = keys.insert(doc.key()); + keys = keys.insert(doc->key()); } return AckTarget(keys); } diff --git a/Firestore/core/test/unit/testutil/view_testing.h b/Firestore/core/test/unit/testutil/view_testing.h index 0f4753c5726..65651d9a9c6 100644 --- a/Firestore/core/test/unit/testutil/view_testing.h +++ b/Firestore/core/test/unit/testutil/view_testing.h @@ -34,8 +34,7 @@ class TargetChange; namespace testutil { /** Converts a list of documents to a sorted map. */ -model::MaybeDocumentMap DocUpdates( - const std::vector& docs); +model::DocumentMap DocUpdates(const std::vector& docs); /** * Computes changes to the view with the docs and then applies them and returns @@ -43,7 +42,7 @@ model::MaybeDocumentMap DocUpdates( */ absl::optional ApplyChanges( core::View* view, - const std::vector& docs, + const std::vector& docs, const absl::optional& target_change); /**