diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index 282f6d6b954..f2b190cc75b 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 022BA1619A576F6818B212C5 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; }; 02C953A7B0FA5EF87DB0361A /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; }; 02EB33CC2590E1484D462912 /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; }; + 033A1FECDD47ED9B1891093B /* arithmetic_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */; }; 035034AB3797D1E5E0112EC3 /* Validation_BloomFilterTest_MD5_1_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 3FDD0050CA08C8302400C5FB /* Validation_BloomFilterTest_MD5_1_1_bloom_filter_proto.json */; }; 035DE410628A8F804F6F2790 /* target_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 526D755F65AC676234F57125 /* target_test.cc */; }; 03AEB9E07A605AE1B5827548 /* field_index_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BF76A8DA34B5B67B4DD74666 /* field_index_test.cc */; }; @@ -127,7 +128,6 @@ 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 */; }; 124AAEE987451820F24EEA8E /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CCC9BD953F121B9E29F9AA42 /* user_test.cc */; }; 125B1048ECB755C2106802EB /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; }; @@ -140,7 +140,6 @@ 12A611A85D59ED2742EEE187 /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 478DC75A0DCA6249A616DD30 /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json */; }; 12BB9ED1CA98AA52B92F497B /* log_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54C2294E1FECABAE007D065B /* log_test.cc */; }; 12DB753599571E24DCED0C2C /* FIRValidationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06D202154D600B64F25 /* FIRValidationTests.mm */; }; - 12E04A12ABD5533B616D552A /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; }; 132E3483789344640A52F223 /* reference_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 132E32997D781B896672D30A /* reference_set_test.cc */; }; 1357806B4CD3A62A8F5DE86D /* http.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */; }; 13D8F4196528BAB19DBB18A7 /* snapshot_version_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */; }; @@ -171,6 +170,7 @@ 1733601ECCEA33E730DEAF45 /* autoid_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A521FC913E500713A1A /* autoid_test.cc */; }; 17473086EBACB98CDC3CC65C /* view_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C7429071B33BDF80A7FA2F8A /* view_test.cc */; }; 17638F813B9B556FE7718C0C /* FIRQuerySnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04F202154AA00B64F25 /* FIRQuerySnapshotTests.mm */; }; + 1792477DD2B3A1710BFD443F /* arithmetic_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */; }; 17DC97DE15D200932174EC1F /* defer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8ABAC2E0402213D837F73DC3 /* defer_test.cc */; }; 17DFF30CF61D87883986E8B6 /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; }; 17ECB768DA44AE0F49647E22 /* memory_query_engine_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8EF6A33BC2D84233C355F1D0 /* memory_query_engine_test.cc */; }; @@ -233,6 +233,7 @@ 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 */; }; 1F56F51EB6DF0951B1F4F85B /* lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 277EAACC4DD7C21332E8496A /* lru_garbage_collector_test.cc */; }; + 1F6319D85C1AFC0D81394470 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */; }; 1F998DDECB54A66222CC66AA /* string_format_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54131E9620ADE678001DF3FF /* string_format_test.cc */; }; 1FE23E911F0761AA896FAD67 /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = D8E530B27D5641B9C26A452C /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json */; }; 2045517602D767BD01EA71D9 /* overlay_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E1459FA70B8FC18DE4B80D0D /* overlay_test.cc */; }; @@ -281,6 +282,7 @@ 26CB3D7C871BC56456C6021E /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; }; 276A563D546698B6AAC20164 /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; }; 27AF4C4BAFE079892D4F5341 /* Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B3E4A77493524333133C5DC /* Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json */; }; + 27B652E6288A9CD1B99E618F /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */; }; 27E46C94AAB087C80A97FF7F /* FIRServerTimestampTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06E202154D600B64F25 /* FIRServerTimestampTests.mm */; }; 280A282BE9AF4DCF4E855EAB /* filesystem_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51859B394D01C0C507282F1 /* filesystem_test.cc */; }; 2836CD14F6F0EA3B184E325E /* schedule_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B0B005A79E765AF02793DCE /* schedule_test.cc */; }; @@ -494,6 +496,7 @@ 4C5292BF643BF14FA2AC5DB1 /* settings_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = DD12BC1DB2480886D2FB0005 /* settings_test.cc */; }; 4C66806697D7BCA730FA3697 /* common.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D221C2DDC800EFB9CC /* common.pb.cc */; }; 4CDFF1AE3D639AA89C5C4411 /* query_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 731541602214AFFA0037F4DC /* query_spec_test.json */; }; + 4CF3DA15D4DF7D038BE13718 /* expression_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = AC64E6C629AAFAC92999B083 /* expression_test_util.cc */; }; 4D1775B7916D4CDAD1BF1876 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; 4D20563D846FA0F3BEBFDE9D /* overlay_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E1459FA70B8FC18DE4B80D0D /* overlay_test.cc */; }; 4D2655C5675D83205C3749DC /* fake_target_metadata_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = 71140E5D09C6E76F7C71B2FC /* fake_target_metadata_provider.cc */; }; @@ -517,6 +520,7 @@ 4F5714D37B6D119CB07ED8AE /* orderby_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A21F315EE100DD57A1 /* orderby_spec_test.json */; }; 4F65FD71B7960944C708A962 /* leveldb_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B629525F7A1AAC1AB765C74F /* leveldb_lru_garbage_collector_test.cc */; }; 4F857404731D45F02C5EE4C3 /* async_queue_libdispatch_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4680208EA0BE00554BA2 /* async_queue_libdispatch_test.mm */; }; + 4F88E2D686CF4C150A29E84E /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */; }; 4FAB27F13EA5D3D79E770EA2 /* ordered_code_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0473AFFF5567E667A125347B /* ordered_code_benchmark.cc */; }; 4FAD8823DC37B9CA24379E85 /* leveldb_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C7942B6244F4C416B11B86C /* leveldb_mutation_queue_test.cc */; }; 50059FDCD2DAAB755FEEEDF2 /* resource.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1C3F7302BF4AE6CBC00ECDD0 /* resource.pb.cc */; }; @@ -749,7 +753,6 @@ 6156C6A837D78D49ED8B8812 /* index_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 8C7278B604B8799F074F4E8C /* index_spec_test.json */; }; 6161B5032047140C00A99DBB /* FIRFirestoreSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6161B5012047140400A99DBB /* FIRFirestoreSourceTests.mm */; }; 618BBEA620B89AAC00B5BCE7 /* target.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */; }; - 618BBEA720B89AAC00B5BCE7 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; }; 618BBEA820B89AAC00B5BCE7 /* mutation.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */; }; 618BBEAE20B89AAC00B5BCE7 /* latlng.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9220B89AAC00B5BCE7 /* latlng.pb.cc */; }; 618BBEAF20B89AAC00B5BCE7 /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; }; @@ -843,7 +846,6 @@ 6F256C06FCBA46378EC35D72 /* leveldb_overlay_migration_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D8A6D52723B1BABE1B7B8D8F /* leveldb_overlay_migration_manager_test.cc */; }; 6F3CAC76D918D6B0917EDF92 /* query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B9C261C26C5D311E1E3C0CB9 /* query_test.cc */; }; 6F45846C159D3C063DBD3CBE /* FirestoreEncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1235769422B86E65007DDFA9 /* FirestoreEncoderTests.swift */; }; - 6F511ABFD023AEB81F92DB12 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; }; 6F67601562343B63B8996F7A /* FSTTestingHooks.mm in Sources */ = {isa = PBXBuildFile; fileRef = D85AC18C55650ED230A71B82 /* FSTTestingHooks.mm */; }; 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 */; }; @@ -977,6 +979,7 @@ 851346D66DEC223E839E3AA9 /* memory_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 74FBEFA4FE4B12C435011763 /* memory_mutation_queue_test.cc */; }; 856A1EAAD674ADBDAAEDAC37 /* bundle_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */; }; 85A33A9CE33207C2333DDD32 /* FIRTransactionOptionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF39ECA1293D21A0A2AB2626 /* FIRTransactionOptionsTests.mm */; }; + 85ADFEB234EBE3D9CDFFCE12 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28034BA61A7395543F1508B3 /* maybe_document.pb.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 */; }; 85D61BDC7FB99B6E0DD3AFCA /* mutation.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */; }; @@ -997,7 +1000,7 @@ 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 */; }; - 88FD82A1FC5FEC5D56B481D8 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; }; + 8976F3D5515C4A784EC6627F /* arithmetic_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */; }; 897F3C1936612ACB018CA1DD /* http.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */; }; 89C71AEAA5316836BB1D5A01 /* view_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C7429071B33BDF80A7FA2F8A /* view_test.cc */; }; 89EB0C7B1241E6F1800A3C7E /* empty_credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8FA60B08D59FEA0D6751E87F /* empty_credentials_provider_test.cc */; }; @@ -1068,6 +1071,7 @@ 977E0DA564D6EAF975A4A1A0 /* settings_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = DD12BC1DB2480886D2FB0005 /* settings_test.cc */; }; 9783FAEA4CF758E8C4C2D76E /* hashing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54511E8D209805F8005BD28F /* hashing_test.cc */; }; 978D9EFDC56CC2E1FA468712 /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; }; + 979840A404FAB985B1D41AA6 /* expression_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = AC64E6C629AAFAC92999B083 /* expression_test_util.cc */; }; 9860F493EBF43AF5AC0A88BD /* empty_credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8FA60B08D59FEA0D6751E87F /* empty_credentials_provider_test.cc */; }; 98708140787A9465D883EEC9 /* leveldb_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C7942B6244F4C416B11B86C /* leveldb_mutation_queue_test.cc */; }; 98FE82875A899A40A98AAC22 /* leveldb_opener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */; }; @@ -1309,6 +1313,7 @@ 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 */; }; BE1D7C7E413449AFFBA21BCB /* overlay_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E1459FA70B8FC18DE4B80D0D /* overlay_test.cc */; }; + BE4C2DFCEEFDC1DC0B37533D /* arithmetic_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 76EED4ED84056B623D92FE20 /* arithmetic_test.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 */; }; BEE0294A23AB993E5DE0E946 /* leveldb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 332485C4DCC6BA0DBB5E31B7 /* leveldb_util_test.cc */; }; @@ -1326,7 +1331,6 @@ C10417B067155BE78E19807D /* FIRIndexingTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 795AA8FC31D2AF6864B07D39 /* FIRIndexingTests.mm */; }; C1237EE2A74F174A3DF5978B /* memory_target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2286F308EFB0534B1BDE05B9 /* memory_target_cache_test.cc */; }; C15F5F1E7427738F20C2D789 /* offline_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A11F315EE100DD57A1 /* offline_spec_test.json */; }; - C19214F5B43AA745A7FC2FC1 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; }; C1B4621C0820EEB0AC9CCD22 /* bits_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D01201BC69F00D97691 /* bits_test.cc */; }; C1C3369C7ECE069B76A84AD1 /* Validation_BloomFilterTest_MD5_500_1_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 8AB49283E544497A9C5A0E59 /* Validation_BloomFilterTest_MD5_500_1_membership_test_result.json */; }; C1CD78F1FDE0918B4F87BC6F /* empty_credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8FA60B08D59FEA0D6751E87F /* empty_credentials_provider_test.cc */; }; @@ -1426,6 +1430,7 @@ D3CB03747E34D7C0365638F1 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_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 */; }; + D4E02FF9F4D517BF5D4F2D14 /* arithmetic_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */; }; D4F85AEACD2FD03C738D1052 /* Validation_BloomFilterTest_MD5_1_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 5C68EE4CB94C0DD6E333F546 /* Validation_BloomFilterTest_MD5_1_01_membership_test_result.json */; }; D50232D696F19C2881AC01CE /* token_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A082AFDD981B07B5AD78FDE8 /* token_test.cc */; }; D550446303227FB1B381133C /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; }; @@ -1473,12 +1478,14 @@ DC0B0E50DBAE916E6565AA18 /* string_win_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 79507DF8378D3C42F5B36268 /* string_win_test.cc */; }; DC0E186BDD221EAE9E4D2F41 /* sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */; }; DC1C711290E12F8EF3601151 /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; }; + DC3351455F8753678905CF73 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */; }; DC48407370E87F2233D7AB7E /* statusor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352D20A3B3D7003E0143 /* statusor_test.cc */; }; DC6804424FC8F7B3044DD0BB /* random_access_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 014C60628830D95031574D15 /* random_access_queue_test.cc */; }; DCC8F3D4AA87C81AB3FD9491 /* md5_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3D050936A2D52257FD17FB6E /* md5_test.cc */; }; DCD83C545D764FB15FD88B02 /* counting_query_engine.cc in Sources */ = {isa = PBXBuildFile; fileRef = 99434327614FEFF7F7DC88EC /* counting_query_engine.cc */; }; DD04F7FE7A1ADE230A247DBC /* byte_stream_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7628664347B9C96462D4BF17 /* byte_stream_apple_test.mm */; }; DD0F288108714D5A406D0A9F /* Validation_BloomFilterTest_MD5_1_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 5C68EE4CB94C0DD6E333F546 /* Validation_BloomFilterTest_MD5_1_01_membership_test_result.json */; }; + DD175F74AC25CC419E874A1D /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */; }; 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 */; }; DD935E243A64A4EB688E4C1C /* credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2F4FA4576525144C5069A7A5 /* credentials_provider_test.cc */; }; @@ -1486,6 +1493,7 @@ DDC782CBA37AA9B0EA373B7A /* explain_stats.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 428662F00938E9E21F7080D7 /* explain_stats.pb.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 */; }; + DDED4752521AF8B347EB6E99 /* expression_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = AC64E6C629AAFAC92999B083 /* expression_test_util.cc */; }; DE03B2D41F2149D600A30B9C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; }; DE03B2D51F2149D600A30B9C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; }; DE03B2D61F2149D600A30B9C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; }; @@ -1554,6 +1562,7 @@ E884336B43BBD1194C17E3C4 /* status_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3CAA33F964042646FDDAF9F9 /* status_testing.cc */; }; E8AB8024B70F6C960D8C7530 /* document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */; }; E8BA7055EDB8B03CC99A528F /* recovery_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 9C1AFCC9E616EC33D6E169CF /* recovery_spec_test.json */; }; + E8BB7CCF3928A5866B1C9B86 /* arithmetic_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */; }; E9071BE412DC42300B936BAF /* explain_stats.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 428662F00938E9E21F7080D7 /* explain_stats.pb.cc */; }; E962CA641FB1312638593131 /* leveldb_document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */; }; E99D5467483B746D4AA44F74 /* fields_array_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BA4CBA48204C9E25B56993BC /* fields_array_test.cc */; }; @@ -1571,6 +1580,7 @@ EBE4A7B6A57BCE02B389E8A6 /* byte_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5342CDDB137B4E93E2E85CCA /* byte_string_test.cc */; }; EBFC611B1BF195D0EC710AF4 /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; }; EC160876D8A42166440E0B53 /* FIRCursorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E070202154D600B64F25 /* FIRCursorTests.mm */; }; + EC1C68ADCA37BFF885671D7A /* expression_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = AC64E6C629AAFAC92999B083 /* expression_test_util.cc */; }; EC3331B17394886A3715CFD8 /* target.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */; }; EC62F9E29CE3598881908FB8 /* leveldb_transaction_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 88CF09277CFA45EE1273E3BA /* leveldb_transaction_test.cc */; }; EC63BD5E46C8734B6D20312D /* Validation_BloomFilterTest_MD5_50000_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 7B44DD11682C4803B73DCC34 /* Validation_BloomFilterTest_MD5_50000_01_bloom_filter_proto.json */; }; @@ -1629,6 +1639,7 @@ F3DEF2DB11FADAABDAA4C8BB /* bundle_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */; }; F3F09BC931A717CEFF4E14B9 /* FIRFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04A202154AA00B64F25 /* FIRFieldValueTests.mm */; }; F481368DB694B3B4D0C8E4A2 /* query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B9C261C26C5D311E1E3C0CB9 /* query_test.cc */; }; + F4DD8315F7F85F9CAB2E7206 /* expression_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = AC64E6C629AAFAC92999B083 /* expression_test_util.cc */; }; F4F00BF4E87D7F0F0F8831DB /* FSTEventAccumulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */; }; F4FAC5A7D40A0A9A3EA77998 /* FSTLevelDBSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02C20213FFB00B64F25 /* FSTLevelDBSpecTests.mm */; }; F563446799EFCF4916758E6C /* Validation_BloomFilterTest_MD5_50000_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 7B44DD11682C4803B73DCC34 /* Validation_BloomFilterTest_MD5_50000_01_bloom_filter_proto.json */; }; @@ -1671,6 +1682,7 @@ FC1D22B6EC4E5F089AE39B8C /* memory_target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2286F308EFB0534B1BDE05B9 /* memory_target_cache_test.cc */; }; FC6C9D1A8B24A5C9507272F7 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; }; FCA48FB54FC50BFDFDA672CD /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; }; + FCBD7D902CEB2A263AF2DE55 /* expression_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = AC64E6C629AAFAC92999B083 /* expression_test_util.cc */; }; FCF8E7F5268F6842C07B69CF /* write.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D921C2DDC800EFB9CC /* write.pb.cc */; }; FD365D6DFE9511D3BA2C74DF /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; }; FD6F5B4497D670330E7F89DA /* document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */; }; @@ -1778,6 +1790,7 @@ 253A7A96FFAA2C8A8754D3CF /* Pods_Firestore_IntegrationTests_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 26DDBA115DEB88631B93F203 /* thread_safe_memoizer_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = thread_safe_memoizer_testing.h; sourceTree = ""; }; 277EAACC4DD7C21332E8496A /* lru_garbage_collector_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = lru_garbage_collector_test.cc; sourceTree = ""; }; + 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = maybe_document.pb.cc; sourceTree = ""; }; 28B45B2104E2DAFBBF86DBB7 /* logic_utils_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = logic_utils_test.cc; sourceTree = ""; }; 29749DC3DADA38CAD1EB9AC4 /* Pods-Firestore_Tests_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_macOS.debug.xcconfig"; path = "Target Support Files/Pods-Firestore_Tests_macOS/Pods-Firestore_Tests_macOS.debug.xcconfig"; sourceTree = ""; }; 29D9C76922DAC6F710BC1EF4 /* memory_document_overlay_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_document_overlay_cache_test.cc; sourceTree = ""; }; @@ -1959,7 +1972,6 @@ 600A7D7D821CE84E0CA8CB89 /* async_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = async_testing.h; sourceTree = ""; }; 6161B5012047140400A99DBB /* FIRFirestoreSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRFirestoreSourceTests.mm; sourceTree = ""; }; 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = target.pb.cc; sourceTree = ""; }; - 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = maybe_document.pb.cc; sourceTree = ""; }; 618BBE7F20B89AAC00B5BCE7 /* target.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = target.pb.h; sourceTree = ""; }; 618BBE8020B89AAC00B5BCE7 /* maybe_document.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = maybe_document.pb.h; sourceTree = ""; }; 618BBE8120B89AAC00B5BCE7 /* mutation.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mutation.pb.h; sourceTree = ""; }; @@ -2008,6 +2020,7 @@ 75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_opener_test.cc; sourceTree = ""; }; 75E24C5CD7BC423D48713100 /* counting_query_engine.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = counting_query_engine.h; sourceTree = ""; }; 7628664347B9C96462D4BF17 /* byte_stream_apple_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = byte_stream_apple_test.mm; sourceTree = ""; }; + 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = arithmetic_test.cc; path = expressions/arithmetic_test.cc; sourceTree = ""; }; 776530F066E788C355B78457 /* FIRBundlesTests.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRBundlesTests.mm; sourceTree = ""; }; 78EE0BFC7E60C4929458A0EA /* resource.pb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = resource.pb.h; sourceTree = ""; }; 79507DF8378D3C42F5B36268 /* string_win_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = string_win_test.cc; sourceTree = ""; }; @@ -2079,6 +2092,7 @@ 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 = ""; }; ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = timestamp_test.cc; sourceTree = ""; }; + AC64E6C629AAFAC92999B083 /* expression_test_util.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = expression_test_util.cc; sourceTree = ""; }; AE4A9E38D65688EE000EE2A1 /* index_manager_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = index_manager_test.cc; sourceTree = ""; }; AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_document_overlay_cache_test.cc; sourceTree = ""; }; AF924C79F49F793992A84879 /* aggregate_query_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = aggregate_query_test.cc; path = api/aggregate_query_test.cc; sourceTree = ""; }; @@ -2126,6 +2140,7 @@ CC572A9168BBEF7B83E4BBC5 /* view_snapshot_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = view_snapshot_test.cc; sourceTree = ""; }; CCC9BD953F121B9E29F9AA42 /* user_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = user_test.cc; path = credentials/user_test.cc; sourceTree = ""; }; CD422AF3E4515FB8E9BE67A0 /* equals_tester.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = equals_tester.h; sourceTree = ""; }; + CDC018C1D4CEC9B131449F98 /* expression_test_util.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = expression_test_util.h; sourceTree = ""; }; CE37875365497FFA8687B745 /* message_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = message_test.cc; path = nanopb/message_test.cc; sourceTree = ""; }; CF39535F2C41AB0006FA6C0E /* create_noop_connectivity_monitor.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = create_noop_connectivity_monitor.cc; sourceTree = ""; }; CF39ECA1293D21A0A2AB2626 /* FIRTransactionOptionsTests.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRTransactionOptionsTests.mm; sourceTree = ""; }; @@ -2387,6 +2402,8 @@ 4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */, 84076EADF6872C78CDAC7291 /* bundle_builder.h */, CD422AF3E4515FB8E9BE67A0 /* equals_tester.h */, + AC64E6C629AAFAC92999B083 /* expression_test_util.cc */, + CDC018C1D4CEC9B131449F98 /* expression_test_util.h */, BA02DA2FCD0001CFC6EB08DA /* filesystem_testing.cc */, 64AA92CFA356A2360F3C5646 /* filesystem_testing.h */, E2E39422953DE1D3C7B97E77 /* md5_testing.cc */, @@ -2782,7 +2799,7 @@ 618BBE7C20B89AAC00B5BCE7 /* local */ = { isa = PBXGroup; children = ( - 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */, + 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */, 618BBE8020B89AAC00B5BCE7 /* maybe_document.pb.h */, 618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */, 618BBE8120B89AAC00B5BCE7 /* mutation.pb.h */, @@ -3000,6 +3017,7 @@ AD2E6E1CDE874DD15298E8F5 /* expressions */ = { isa = PBXGroup; children = ( + 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */, 87DD1A65EBA9FFC1FFAAE657 /* comparison_test.cc */, ); name = expressions; @@ -4278,6 +4296,7 @@ C8BA36C8B5E26C173F91E677 /* aggregation_result.pb.cc in Sources */, 45939AFF906155EA27D281AB /* annotations.pb.cc in Sources */, FF3405218188DFCE586FB26B /* app_testing.mm in Sources */, + E8BB7CCF3928A5866B1C9B86 /* arithmetic_test.cc in Sources */, B192F30DECA8C28007F9B1D0 /* array_sorted_map_test.cc in Sources */, 4F857404731D45F02C5EE4C3 /* async_queue_libdispatch_test.mm in Sources */, 83A9CD3B6E791A860CE81FA1 /* async_queue_std_test.cc in Sources */, @@ -4300,8 +4319,8 @@ 9AC604BF7A76CABDF26F8C8E /* cc_compilation_test.cc in Sources */, 1B730A4E8C4BD7B5B0FF9C7F /* collection_test.cc in Sources */, 5556B648B9B1C2F79A706B4F /* common.pb.cc in Sources */, - 08D853C9D3A4DC919C55671A /* comparison_test.cc in Sources */, 11627F3A48F710D654829807 /* comparison_test.cc in Sources */, + 08D853C9D3A4DC919C55671A /* comparison_test.cc in Sources */, 3095316962A00DD6A4A2A441 /* counting_query_engine.cc in Sources */, 4D903ED7B7E4D38F988CD3F8 /* create_noop_connectivity_monitor.cc in Sources */, 9BEC62D59EB2C68342F493CD /* credentials_provider_test.cc in Sources */, @@ -4322,6 +4341,7 @@ 470A37727BBF516B05ED276A /* executor_test.cc in Sources */, 2F72DBE2EC6E24A81C69DEF0 /* explain_stats.pb.cc in Sources */, 2E0BBA7E627EB240BA11B0D0 /* exponential_backoff_test.cc in Sources */, + FCBD7D902CEB2A263AF2DE55 /* expression_test_util.cc in Sources */, 9009C285F418EA80C46CF06B /* fake_target_metadata_provider.cc in Sources */, 7B58861D0978827BC4CB1DFA /* field_behavior.pb.cc in Sources */, 2E373EA9D5FF8C6DE2507675 /* field_index_test.cc in Sources */, @@ -4374,7 +4394,7 @@ DBDC8E997E909804F1B43E92 /* log_test.cc in Sources */, F924DF3D9DCD2720C315A372 /* logic_utils_test.cc in Sources */, 3F6C9F8A993CF4B0CD51E7F0 /* lru_garbage_collector_test.cc in Sources */, - 12158DFCEE09D24B7988A340 /* maybe_document.pb.cc in Sources */, + 1F6319D85C1AFC0D81394470 /* maybe_document.pb.cc in Sources */, 380E543B7BC6F648BBB250B4 /* md5_test.cc in Sources */, FE20E696E014CDCE918E91D6 /* md5_testing.cc in Sources */, FA43BA0195DA90CE29B29D36 /* memory_bundle_cache_test.cc in Sources */, @@ -4506,6 +4526,7 @@ 156429A2993B86A905A42D96 /* aggregation_result.pb.cc in Sources */, 1C19D796DB6715368407387A /* annotations.pb.cc in Sources */, 6EEA00A737690EF82A3C91C6 /* app_testing.mm in Sources */, + 033A1FECDD47ED9B1891093B /* arithmetic_test.cc in Sources */, 1291D9F5300AFACD1FBD262D /* array_sorted_map_test.cc in Sources */, 4AD9809C9CE9FA09AC40992F /* async_queue_libdispatch_test.mm in Sources */, 38208AC761FF994BA69822BE /* async_queue_std_test.cc in Sources */, @@ -4528,8 +4549,8 @@ 079E63E270F3EFCA175D2705 /* cc_compilation_test.cc in Sources */, 0480559E91BB66732ABE45C8 /* collection_test.cc in Sources */, 18638EAED9E126FC5D895B14 /* common.pb.cc in Sources */, - 1115DB1F1DCE93B63E03BA8C /* comparison_test.cc in Sources */, 6888F84253360455023C600B /* comparison_test.cc in Sources */, + 1115DB1F1DCE93B63E03BA8C /* comparison_test.cc in Sources */, 2A0925323776AD50C1105BC0 /* counting_query_engine.cc in Sources */, AEE9105543013C9C89FAB2B5 /* create_noop_connectivity_monitor.cc in Sources */, B6BF87E3C9A72DCB8C5DB754 /* credentials_provider_test.cc in Sources */, @@ -4550,6 +4571,7 @@ 3A7CB01751697ED599F2D9A1 /* executor_test.cc in Sources */, 7CAF0E8C47FB2DD486240D47 /* explain_stats.pb.cc in Sources */, EF3518F84255BAF3EBD317F6 /* exponential_backoff_test.cc in Sources */, + 979840A404FAB985B1D41AA6 /* expression_test_util.cc in Sources */, 4DAFC3A3FD5E96910A517320 /* fake_target_metadata_provider.cc in Sources */, E9BC6A5BC2B209B1BA2F8BD6 /* field_behavior.pb.cc in Sources */, 69D3AD697D1A7BF803A08160 /* field_index_test.cc in Sources */, @@ -4602,7 +4624,7 @@ 12BB9ED1CA98AA52B92F497B /* log_test.cc in Sources */, 7EF56BA2A480026D62CCA35A /* logic_utils_test.cc in Sources */, 1F56F51EB6DF0951B1F4F85B /* lru_garbage_collector_test.cc in Sources */, - 88FD82A1FC5FEC5D56B481D8 /* maybe_document.pb.cc in Sources */, + DD175F74AC25CC419E874A1D /* maybe_document.pb.cc in Sources */, DCC8F3D4AA87C81AB3FD9491 /* md5_test.cc in Sources */, 169EDCF15637580BA79B61AD /* md5_testing.cc in Sources */, 9611A0FAA2E10A6B1C1AC2EA /* memory_bundle_cache_test.cc in Sources */, @@ -4760,6 +4782,7 @@ 0EC3921AE220410F7394729B /* aggregation_result.pb.cc in Sources */, 276A563D546698B6AAC20164 /* annotations.pb.cc in Sources */, 7B8D7BAC1A075DB773230505 /* app_testing.mm in Sources */, + 8976F3D5515C4A784EC6627F /* arithmetic_test.cc in Sources */, DC1C711290E12F8EF3601151 /* array_sorted_map_test.cc in Sources */, 9B2CD4CBB1DFE8BC3C81A335 /* async_queue_libdispatch_test.mm in Sources */, 342724CA250A65E23CB133AC /* async_queue_std_test.cc in Sources */, @@ -4782,8 +4805,8 @@ 0A52B47C43B7602EE64F53A7 /* cc_compilation_test.cc in Sources */, 064689971747DA312770AB7A /* collection_test.cc in Sources */, 1DB3013C5FC736B519CD65A3 /* common.pb.cc in Sources */, - 555161D6DB2DDC8B57F72A70 /* comparison_test.cc in Sources */, 99F97B28DA546D42AB14214B /* comparison_test.cc in Sources */, + 555161D6DB2DDC8B57F72A70 /* comparison_test.cc in Sources */, 7394B5C29C6E524C2AF964E6 /* counting_query_engine.cc in Sources */, C02A969BF4BB63ABCB531B4B /* create_noop_connectivity_monitor.cc in Sources */, DD935E243A64A4EB688E4C1C /* credentials_provider_test.cc in Sources */, @@ -4804,6 +4827,7 @@ 18F644E6AA98E6D6F3F1F809 /* executor_test.cc in Sources */, ABE599C3BF9FB6AFF18AA901 /* explain_stats.pb.cc in Sources */, 6938575C8B5E6FE0D562547A /* exponential_backoff_test.cc in Sources */, + 4CF3DA15D4DF7D038BE13718 /* expression_test_util.cc in Sources */, 258B372CF33B7E7984BBA659 /* fake_target_metadata_provider.cc in Sources */, 2FC2B732841BF2C425EB35DF /* field_behavior.pb.cc in Sources */, F8BD2F61EFA35C2D5120D9EB /* field_index_test.cc in Sources */, @@ -4856,7 +4880,7 @@ CAFB1E0ED514FEF4641E3605 /* log_test.cc in Sources */, 0595B5EBEB8F09952B72C883 /* logic_utils_test.cc in Sources */, 913F6E57AF18F84C5ECFD414 /* lru_garbage_collector_test.cc in Sources */, - 6F511ABFD023AEB81F92DB12 /* maybe_document.pb.cc in Sources */, + 27B652E6288A9CD1B99E618F /* maybe_document.pb.cc in Sources */, 13ED75EFC2F6917951518A4B /* md5_test.cc in Sources */, E2AC3BDAAFFF9A45C916708B /* md5_testing.cc in Sources */, FF6333B8BD9732C068157221 /* memory_bundle_cache_test.cc in Sources */, @@ -5014,6 +5038,7 @@ DF983A9C1FBF758AF3AF110D /* aggregation_result.pb.cc in Sources */, EA46611779C3EEF12822508C /* annotations.pb.cc in Sources */, 8F4F40E9BC7ED588F67734D5 /* app_testing.mm in Sources */, + BE4C2DFCEEFDC1DC0B37533D /* arithmetic_test.cc in Sources */, A6E236CE8B3A47BE32254436 /* array_sorted_map_test.cc in Sources */, 1CB8AEFBF3E9565FF9955B50 /* async_queue_libdispatch_test.mm in Sources */, AB2BAB0BD77FF05CC26FCF75 /* async_queue_std_test.cc in Sources */, @@ -5036,8 +5061,8 @@ 1E8A00ABF414AC6C6591D9AC /* cc_compilation_test.cc in Sources */, C87DF880BADEA1CBF8365700 /* collection_test.cc in Sources */, 1D71CA6BBA1E3433F243188E /* common.pb.cc in Sources */, - 9C86EEDEA131BFD50255EEF1 /* comparison_test.cc in Sources */, 476AE05E0878007DE1BF5460 /* comparison_test.cc in Sources */, + 9C86EEDEA131BFD50255EEF1 /* comparison_test.cc in Sources */, DCD83C545D764FB15FD88B02 /* counting_query_engine.cc in Sources */, ECC433628575AE994C621C54 /* create_noop_connectivity_monitor.cc in Sources */, 6E7603BC1D8011A5D6F62072 /* credentials_provider_test.cc in Sources */, @@ -5058,6 +5083,7 @@ 814724DE70EFC3DDF439CD78 /* executor_test.cc in Sources */, A296B0110550890E1D8D59A3 /* explain_stats.pb.cc in Sources */, BD6CC8614970A3D7D2CF0D49 /* exponential_backoff_test.cc in Sources */, + DDED4752521AF8B347EB6E99 /* expression_test_util.cc in Sources */, 4D2655C5675D83205C3749DC /* fake_target_metadata_provider.cc in Sources */, FB462B2C6D3C167DF32BA0E1 /* field_behavior.pb.cc in Sources */, 50C852E08626CFA7DC889EEA /* field_index_test.cc in Sources */, @@ -5110,7 +5136,7 @@ 6B94E0AE1002C5C9EA0F5582 /* log_test.cc in Sources */, 0D6AE96565603226DB2E6838 /* logic_utils_test.cc in Sources */, 95CE3F5265B9BB7297EE5A6B /* lru_garbage_collector_test.cc in Sources */, - C19214F5B43AA745A7FC2FC1 /* maybe_document.pb.cc in Sources */, + 4F88E2D686CF4C150A29E84E /* maybe_document.pb.cc in Sources */, 211A60ECA3976D27C0BF59BB /* md5_test.cc in Sources */, E72A77095FF6814267DF0F6D /* md5_testing.cc in Sources */, 94854FAEAEA75A1AC77A0515 /* memory_bundle_cache_test.cc in Sources */, @@ -5252,6 +5278,7 @@ B81B6F327B5E3FE820DC3FB3 /* aggregation_result.pb.cc in Sources */, 618BBEAF20B89AAC00B5BCE7 /* annotations.pb.cc in Sources */, 5467FB08203E6A44009C9584 /* app_testing.mm in Sources */, + D4E02FF9F4D517BF5D4F2D14 /* arithmetic_test.cc in Sources */, 54EB764D202277B30088B8F3 /* array_sorted_map_test.cc in Sources */, B6FB4684208EA0EC00554BA2 /* async_queue_libdispatch_test.mm in Sources */, B6FB4685208EA0F000554BA2 /* async_queue_std_test.cc in Sources */, @@ -5274,8 +5301,8 @@ 08A9C531265B5E4C5367346E /* cc_compilation_test.cc in Sources */, C551536B0BAE9EB452DD6758 /* collection_test.cc in Sources */, 544129DA21C2DDC800EFB9CC /* common.pb.cc in Sources */, - 548DB929200D59F600E00ABC /* comparison_test.cc in Sources */, 95490163C98C4F8AFD019730 /* comparison_test.cc in Sources */, + 548DB929200D59F600E00ABC /* comparison_test.cc in Sources */, 4E2E0314F9FDD7BCED60254A /* counting_query_engine.cc in Sources */, 1989623826923A9D5A7EFA40 /* create_noop_connectivity_monitor.cc in Sources */, E8608D40B683938C6D785627 /* credentials_provider_test.cc in Sources */, @@ -5296,6 +5323,7 @@ B6FB4690208F9BB300554BA2 /* executor_test.cc in Sources */, DDC782CBA37AA9B0EA373B7A /* explain_stats.pb.cc in Sources */, B6D1B68520E2AB1B00B35856 /* exponential_backoff_test.cc in Sources */, + EC1C68ADCA37BFF885671D7A /* expression_test_util.cc in Sources */, FAE5DA6ED3E1842DC21453EE /* fake_target_metadata_provider.cc in Sources */, F21A3E06BBEC807FADB43AAF /* field_behavior.pb.cc in Sources */, 03AEB9E07A605AE1B5827548 /* field_index_test.cc in Sources */, @@ -5348,7 +5376,7 @@ 54C2294F1FECABAE007D065B /* log_test.cc in Sources */, D156B9F19B5B29E77664FDFC /* logic_utils_test.cc in Sources */, 1290FA77A922B76503AE407C /* lru_garbage_collector_test.cc in Sources */, - 618BBEA720B89AAC00B5BCE7 /* maybe_document.pb.cc in Sources */, + 85ADFEB234EBE3D9CDFFCE12 /* maybe_document.pb.cc in Sources */, C86E85101352B5CDBF5909F9 /* md5_test.cc in Sources */, 723BBD713478BB26CEFA5A7D /* md5_testing.cc in Sources */, A0E1C7F5C7093A498F65C5CF /* memory_bundle_cache_test.cc in Sources */, @@ -5525,6 +5553,7 @@ 1A3D8028303B45FCBB21CAD3 /* aggregation_result.pb.cc in Sources */, 02EB33CC2590E1484D462912 /* annotations.pb.cc in Sources */, EBFC611B1BF195D0EC710AF4 /* app_testing.mm in Sources */, + 1792477DD2B3A1710BFD443F /* arithmetic_test.cc in Sources */, FCA48FB54FC50BFDFDA672CD /* array_sorted_map_test.cc in Sources */, 45A5504D33D39C6F80302450 /* async_queue_libdispatch_test.mm in Sources */, 6F914209F46E6552B5A79570 /* async_queue_std_test.cc in Sources */, @@ -5547,8 +5576,8 @@ 338DFD5BCD142DF6C82A0D56 /* cc_compilation_test.cc in Sources */, BACA9CDF0F2E926926B5F36F /* collection_test.cc in Sources */, 4C66806697D7BCA730FA3697 /* common.pb.cc in Sources */, - EC7A44792A5513FBB6F501EE /* comparison_test.cc in Sources */, C885C84B7549C860784E4E3C /* comparison_test.cc in Sources */, + EC7A44792A5513FBB6F501EE /* comparison_test.cc in Sources */, BDF3A6C121F2773BB3A347A7 /* counting_query_engine.cc in Sources */, 1F4930A8366F74288121F627 /* create_noop_connectivity_monitor.cc in Sources */, 7DE2560C3B4EF0512F0D538C /* credentials_provider_test.cc in Sources */, @@ -5569,6 +5598,7 @@ DABB9FB61B1733F985CBF713 /* executor_test.cc in Sources */, E9071BE412DC42300B936BAF /* explain_stats.pb.cc in Sources */, 7BCF050BA04537B0E7D44730 /* exponential_backoff_test.cc in Sources */, + F4DD8315F7F85F9CAB2E7206 /* expression_test_util.cc in Sources */, BA1C5EAE87393D8E60F5AE6D /* fake_target_metadata_provider.cc in Sources */, 3A110ECBF96B6E44BA77011A /* field_behavior.pb.cc in Sources */, 84285C3F63D916A4786724A8 /* field_index_test.cc in Sources */, @@ -5621,7 +5651,7 @@ 677C833244550767B71DB1BA /* log_test.cc in Sources */, 6FCC64A1937E286E76C294D0 /* logic_utils_test.cc in Sources */, 4DF18D15AC926FB7A4888313 /* lru_garbage_collector_test.cc in Sources */, - 12E04A12ABD5533B616D552A /* maybe_document.pb.cc in Sources */, + DC3351455F8753678905CF73 /* maybe_document.pb.cc in Sources */, E74D6C1056DE29969B5C4C62 /* md5_test.cc in Sources */, 1DCDED1F94EBC7F72FDBFC98 /* md5_testing.cc in Sources */, 479A392EAB42453D49435D28 /* memory_bundle_cache_test.cc in Sources */, diff --git a/Firestore/core/src/core/expressions_eval.cc b/Firestore/core/src/core/expressions_eval.cc index 5ff34e170db..7ffe03440f1 100644 --- a/Firestore/core/src/core/expressions_eval.cc +++ b/Firestore/core/src/core/expressions_eval.cc @@ -16,17 +16,221 @@ #include "Firestore/core/src/core/expressions_eval.h" +#include +#include #include +#include // For std::move #include "Firestore/core/src/api/expressions.h" #include "Firestore/core/src/api/stages.h" #include "Firestore/core/src/model/mutable_document.h" +#include "Firestore/core/src/model/value_util.h" // Added for value helpers +#include "Firestore/core/src/nanopb/message.h" // Added for MakeMessage #include "Firestore/core/src/remote/serializer.h" +#include "Firestore/core/src/util/hard_assert.h" // Added for HARD_ASSERT +#include "absl/types/optional.h" // Added for absl::optional namespace firebase { namespace firestore { namespace core { +namespace { + +// Helper functions for safe integer arithmetic with overflow detection. +// Return nullopt on overflow or error (like division by zero). + +absl::optional SafeAdd(int64_t lhs, int64_t rhs) { + int64_t result; +#if defined(__clang__) || defined(__GNUC__) + if (__builtin_add_overflow(lhs, rhs, &result)) { + return absl::nullopt; + } +#else + // Manual check (less efficient, might miss some edge cases on weird + // platforms) + if ((rhs > 0 && lhs > std::numeric_limits::max() - rhs) || + (rhs < 0 && lhs < std::numeric_limits::min() - rhs)) { + return absl::nullopt; + } + result = lhs + rhs; +#endif + return result; +} + +absl::optional SafeSubtract(int64_t lhs, int64_t rhs) { + int64_t result; +#if defined(__clang__) || defined(__GNUC__) + if (__builtin_sub_overflow(lhs, rhs, &result)) { + return absl::nullopt; + } +#else + // Manual check + if ((rhs < 0 && lhs > std::numeric_limits::max() + rhs) || + (rhs > 0 && lhs < std::numeric_limits::min() + rhs)) { + return absl::nullopt; + } + result = lhs - rhs; +#endif + return result; +} + +absl::optional SafeMultiply(int64_t lhs, int64_t rhs) { + int64_t result; +#if defined(__clang__) || defined(__GNUC__) + if (__builtin_mul_overflow(lhs, rhs, &result)) { + return absl::nullopt; + } +#else + // Manual check (simplified, might not cover all edge cases perfectly) + if (lhs != 0 && rhs != 0) { + if (lhs > std::numeric_limits::max() / rhs || + lhs < std::numeric_limits::min() / rhs) { + return absl::nullopt; + } + } + result = lhs * rhs; +#endif + return result; +} + +absl::optional SafeDivide(int64_t lhs, int64_t rhs) { + if (rhs == 0) { + return absl::nullopt; // Division by zero + } + // Check for overflow: INT64_MIN / -1 + if (lhs == std::numeric_limits::min() && rhs == -1) { + return absl::nullopt; + } + return lhs / rhs; +} + +absl::optional SafeMod(int64_t lhs, int64_t rhs) { + if (rhs == 0) { + return absl::nullopt; // Modulo by zero + } + // Check for potential overflow/UB: INT64_MIN % -1 + if (lhs == std::numeric_limits::min() && rhs == -1) { + // The result is 0 on most platforms, but standard allows signal. + // Treat as error for consistency. + return absl::nullopt; + } + return lhs % rhs; +} + +// Helper to get double value, converting integer if necessary. +absl::optional GetDoubleValue(const google_firestore_v1_Value& value) { + if (model::IsDouble(value)) { + return value.double_value; + } else if (model::IsInteger(value)) { + return static_cast(value.integer_value); + } + return absl::nullopt; +} + +// Helper to create a Value proto from int64_t +nanopb::Message IntValue(int64_t val) { + google_firestore_v1_Value proto; + proto.which_value_type = google_firestore_v1_Value_integer_value_tag; + proto.integer_value = val; + return nanopb::MakeMessage(std::move(proto)); +} + +// Helper to create a Value proto from double +nanopb::Message DoubleValue(double val) { + google_firestore_v1_Value proto; + proto.which_value_type = google_firestore_v1_Value_double_value_tag; + proto.double_value = val; + return nanopb::MakeMessage(std::move(proto)); +} + +// Common evaluation logic for binary arithmetic operations +template +EvaluateResult EvaluateArithmetic(const api::FunctionExpr* expr, + const api::EvaluateContext& context, + const model::PipelineInputOutput& document, + IntOp int_op, + DoubleOp double_op) { + HARD_ASSERT(expr->params().size() >= 2, + "%s() function requires at least 2 params", expr->name()); + + EvaluateResult current_result = + expr->params()[0]->ToEvaluable()->Evaluate(context, document); + + for (size_t i = 1; i < expr->params().size(); ++i) { + if (current_result.IsErrorOrUnset()) { + return EvaluateResult::NewError(); + } + if (current_result.IsNull()) { + // Null propagates + return EvaluateResult::NewNull(); + } + + EvaluateResult next_operand = + expr->params()[i]->ToEvaluable()->Evaluate(context, document); + + if (next_operand.IsErrorOrUnset()) { + return EvaluateResult::NewError(); + } + if (next_operand.IsNull()) { + // Null propagates + return EvaluateResult::NewNull(); + } + + const google_firestore_v1_Value* left_val = current_result.value(); + const google_firestore_v1_Value* right_val = next_operand.value(); + + // Type checking + bool left_is_num = model::IsNumber(*left_val); + bool right_is_num = model::IsNumber(*right_val); + + if (!left_is_num || !right_is_num) { + return EvaluateResult::NewError(); // Type error + } + + // NaN propagation + if (model::IsNaNValue(*left_val) || model::IsNaNValue(*right_val)) { + current_result = + EvaluateResult::NewValue(nanopb::MakeMessage(model::NaNValue())); + continue; + } + + // Perform arithmetic + if (model::IsDouble(*left_val) || model::IsDouble(*right_val)) { + // Promote to double + absl::optional left_double = GetDoubleValue(*left_val); + absl::optional right_double = GetDoubleValue(*right_val); + // Should always succeed due to IsNumber check above + HARD_ASSERT(left_double.has_value() && right_double.has_value(), + "Failed to extract double values"); + + double result_double = + double_op(left_double.value(), right_double.value()); + current_result = EvaluateResult::NewValue(DoubleValue(result_double)); + + } else { + // Both are integers + absl::optional left_int = model::GetInteger(*left_val); + absl::optional right_int = model::GetInteger(*right_val); + // Should always succeed due to IsNumber check above + HARD_ASSERT(left_int.has_value() && right_int.has_value(), + "Failed to extract integer values"); + + absl::optional result_int = + int_op(left_int.value(), right_int.value()); + + if (!result_int.has_value()) { + // Overflow or division/mod by zero + return EvaluateResult::NewError(); + } + current_result = EvaluateResult::NewValue(IntValue(result_int.value())); + } + } + + return current_result; +} + +} // anonymous namespace + EvaluateResult::EvaluateResult( EvaluateResult::ResultType type, nanopb::Message message) @@ -81,9 +285,30 @@ std::unique_ptr FunctionToEvaluable( const api::FunctionExpr& function) { if (function.name() == "eq") { return std::make_unique(function); + } else if (function.name() == "add") { + return std::make_unique(function); + } else if (function.name() == "subtract") { + return std::make_unique(function); + } else if (function.name() == "multiply") { + return std::make_unique(function); + } else if (function.name() == "divide") { + return std::make_unique(function); + } else if (function.name() == "mod") { + return std::make_unique(function); + } else if (function.name() == "neq") { + return std::make_unique(function); + } else if (function.name() == "lt") { + return std::make_unique(function); + } else if (function.name() == "lte") { + return std::make_unique(function); + } else if (function.name() == "gt") { + return std::make_unique(function); + } else if (function.name() == "gte") { + return std::make_unique(function); } + // TODO(wuandy): Add other functions - return nullptr; + HARD_FAIL("Unsupported function name: %s", function.name()); } EvaluateResult CoreField::Evaluate( @@ -127,53 +352,228 @@ EvaluateResult CoreConstant::Evaluate(const api::EvaluateContext&, return EvaluateResult::NewValue(nanopb::MakeMessage(constant->to_proto())); } -EvaluateResult CoreEq::Evaluate( +// --- Comparison Implementations --- + +EvaluateResult ComparisonBase::Evaluate( const api::EvaluateContext& context, const model::PipelineInputOutput& document) const { - auto* api_eq = expr_.get(); - HARD_ASSERT(api_eq->params().size() == 2, - "%s() function should have exactly 2 params", api_eq->name()); - - const auto left = - api_eq->params()[0]->ToEvaluable()->Evaluate(context, document); - switch (left.type()) { - case EvaluateResult::ResultType::kError: - return EvaluateResult::NewError(); - case EvaluateResult::ResultType::kUnset: - return EvaluateResult::NewUnset(); - default: { - } + HARD_ASSERT(expr_->params().size() == 2, + "%s() function requires exactly 2 params", expr_->name()); + + std::unique_ptr left_evaluable = + expr_->params()[0]->ToEvaluable(); + std::unique_ptr right_evaluable = + expr_->params()[1]->ToEvaluable(); + + EvaluateResult left = left_evaluable->Evaluate(context, document); + if (left.IsErrorOrUnset()) { + return left; // Propagate Error or Unset } - const auto right = - api_eq->params()[1]->ToEvaluable()->Evaluate(context, document); - switch (right.type()) { - case EvaluateResult::ResultType::kError: - return EvaluateResult::NewError(); - case EvaluateResult::ResultType::kUnset: - return EvaluateResult::NewUnset(); - default: { - } + EvaluateResult right = right_evaluable->Evaluate(context, document); + if (right.IsErrorOrUnset()) { + return right; // Propagate Error or Unset } + // Comparisons involving Null propagate Null if (left.IsNull() || right.IsNull()) { return EvaluateResult::NewNull(); } + // Operands are valid Values, proceed with specific comparison + return CompareToResult(left, right); +} + +EvaluateResult CoreEq::CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const { + // Type mismatch always results in false for Eq if (model::GetTypeOrder(*left.value()) != model::GetTypeOrder(*right.value())) { return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); } + // NaN == anything (including NaN) is false if (model::IsNaNValue(*left.value()) || model::IsNaNValue(*right.value())) { return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); } - // TODO(pipeline): Port strictEquals from web - if (model::Equals(*left.value(), *right.value())) { + switch (model::StrictEquals(*left.value(), *right.value())) { + case model::StrictEqualsResult::kEq: + return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue())); + case model::StrictEqualsResult::kNotEq: + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + case model::StrictEqualsResult::kNull: + return EvaluateResult::NewNull(); + } +} + +EvaluateResult CoreNeq::CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const { + // NaN != anything (including NaN) is true + if (model::IsNaNValue(*left.value()) || model::IsNaNValue(*right.value())) { return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue())); - } else { + } + // Type mismatch always results in true for Neq + if (model::GetTypeOrder(*left.value()) != + model::GetTypeOrder(*right.value())) { + return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue())); + } + + switch (model::StrictEquals(*left.value(), *right.value())) { + case model::StrictEqualsResult::kEq: + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + case model::StrictEqualsResult::kNotEq: + return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue())); + case model::StrictEqualsResult::kNull: + return EvaluateResult::NewNull(); + } +} + +EvaluateResult CoreLt::CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const { + // Type mismatch always results in false + if (model::GetTypeOrder(*left.value()) != + model::GetTypeOrder(*right.value())) { + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + } + // NaN compared to anything is false + if (model::IsNaNValue(*left.value()) || model::IsNaNValue(*right.value())) { + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + } + + bool result = model::Compare(*left.value(), *right.value()) == + util::ComparisonResult::Ascending; + return EvaluateResult::NewValue( + nanopb::MakeMessage(result ? model::TrueValue() : model::FalseValue())); +} + +EvaluateResult CoreLte::CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const { + // Type mismatch always results in false + if (model::GetTypeOrder(*left.value()) != + model::GetTypeOrder(*right.value())) { + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + } + // NaN compared to anything is false + if (model::IsNaNValue(*left.value()) || model::IsNaNValue(*right.value())) { + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + } + + // Check for equality first using StrictEquals + if (model::StrictEquals(*left.value(), *right.value()) == + model::StrictEqualsResult::kEq) { + return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue())); + } + + // If not equal, perform standard comparison + bool result = model::Compare(*left.value(), *right.value()) == + util::ComparisonResult::Ascending; + return EvaluateResult::NewValue( + nanopb::MakeMessage(result ? model::TrueValue() : model::FalseValue())); +} + +EvaluateResult CoreGt::CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const { + // Type mismatch always results in false + if (model::GetTypeOrder(*left.value()) != + model::GetTypeOrder(*right.value())) { + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + } + // NaN compared to anything is false + if (model::IsNaNValue(*left.value()) || model::IsNaNValue(*right.value())) { return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); } + + bool result = model::Compare(*left.value(), *right.value()) == + util::ComparisonResult::Descending; + return EvaluateResult::NewValue( + nanopb::MakeMessage(result ? model::TrueValue() : model::FalseValue())); +} + +EvaluateResult CoreGte::CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const { + // Type mismatch always results in false + if (model::GetTypeOrder(*left.value()) != + model::GetTypeOrder(*right.value())) { + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + } + // NaN compared to anything is false + if (model::IsNaNValue(*left.value()) || model::IsNaNValue(*right.value())) { + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + } + + // Check for equality first using StrictEquals + if (model::StrictEquals(*left.value(), *right.value()) == + model::StrictEqualsResult::kEq) { + return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue())); + } + + // If not equal, perform standard comparison + bool result = model::Compare(*left.value(), *right.value()) == + util::ComparisonResult::Descending; + return EvaluateResult::NewValue( + nanopb::MakeMessage(result ? model::TrueValue() : model::FalseValue())); +} + +// --- Arithmetic Implementations --- + +EvaluateResult CoreAdd::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + return EvaluateArithmetic( + expr_.get(), context, document, + [](int64_t l, int64_t r) { return SafeAdd(l, r); }, + [](double l, double r) { return l + r; }); +} + +EvaluateResult CoreSubtract::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + return EvaluateArithmetic( + expr_.get(), context, document, + [](int64_t l, int64_t r) { return SafeSubtract(l, r); }, + [](double l, double r) { return l - r; }); +} + +EvaluateResult CoreMultiply::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + return EvaluateArithmetic( + expr_.get(), context, document, + [](int64_t l, int64_t r) { return SafeMultiply(l, r); }, + [](double l, double r) { return l * r; }); +} + +EvaluateResult CoreDivide::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + return EvaluateArithmetic( + expr_.get(), context, document, + // Integer division + [](int64_t l, int64_t r) { return SafeDivide(l, r); }, + // Double division + [](double l, double r) { + // C++ double division handles signed zero correctly according to IEEE + // 754. +x / +0 -> +Inf -x / +0 -> -Inf +x / -0 -> -Inf -x / -0 -> +Inf + // 0 / 0 -> NaN + return l / r; + }); +} + +EvaluateResult CoreMod::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + return EvaluateArithmetic( + expr_.get(), context, document, + // Integer modulo + [](int64_t l, int64_t r) { return SafeMod(l, r); }, + // Double modulo + [](double l, double r) { + if (r == 0.0) { + return std::numeric_limits::quiet_NaN(); + } + // Use std::fmod for double modulo, matches C++ and Firestore semantics + return std::fmod(l, r); + }); } } // namespace core diff --git a/Firestore/core/src/core/expressions_eval.h b/Firestore/core/src/core/expressions_eval.h index e20db2c0b1d..398cfd25c04 100644 --- a/Firestore/core/src/core/expressions_eval.h +++ b/Firestore/core/src/core/expressions_eval.h @@ -135,9 +135,147 @@ class CoreConstant : public EvaluableExpr { std::unique_ptr expr_; }; -class CoreEq : public EvaluableExpr { +/** Base class for binary comparison expressions (==, !=, <, <=, >, >=). */ +class ComparisonBase : public EvaluableExpr { public: - explicit CoreEq(const api::FunctionExpr& expr) + explicit ComparisonBase(const api::FunctionExpr& expr) + : expr_(std::make_unique(expr)) { + } + + EvaluateResult Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const override; + + protected: + /** + * Performs the specific comparison logic after operands have been evaluated + * and basic checks (Error, Unset, Null) have passed. + */ + virtual EvaluateResult CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const = 0; + + std::unique_ptr expr_; +}; + +class CoreEq : public ComparisonBase { + public: + explicit CoreEq(const api::FunctionExpr& expr) : ComparisonBase(expr) { + } + + protected: + EvaluateResult CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const override; +}; + +class CoreNeq : public ComparisonBase { + public: + explicit CoreNeq(const api::FunctionExpr& expr) : ComparisonBase(expr) { + } + + protected: + EvaluateResult CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const override; +}; + +class CoreLt : public ComparisonBase { + public: + explicit CoreLt(const api::FunctionExpr& expr) : ComparisonBase(expr) { + } + + protected: + EvaluateResult CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const override; +}; + +class CoreLte : public ComparisonBase { + public: + explicit CoreLte(const api::FunctionExpr& expr) : ComparisonBase(expr) { + } + + protected: + EvaluateResult CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const override; +}; + +class CoreGt : public ComparisonBase { + public: + explicit CoreGt(const api::FunctionExpr& expr) : ComparisonBase(expr) { + } + + protected: + EvaluateResult CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const override; +}; + +class CoreGte : public ComparisonBase { + public: + explicit CoreGte(const api::FunctionExpr& expr) : ComparisonBase(expr) { + } + + protected: + EvaluateResult CompareToResult(const EvaluateResult& left, + const EvaluateResult& right) const override; +}; + +class CoreAdd : public EvaluableExpr { + public: + explicit CoreAdd(const api::FunctionExpr& expr) + : expr_(std::make_unique(expr)) { + } + + EvaluateResult Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const override; + + private: + std::unique_ptr expr_; +}; + +class CoreSubtract : public EvaluableExpr { + public: + explicit CoreSubtract(const api::FunctionExpr& expr) + : expr_(std::make_unique(expr)) { + } + + EvaluateResult Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const override; + + private: + std::unique_ptr expr_; +}; + +class CoreMultiply : public EvaluableExpr { + public: + explicit CoreMultiply(const api::FunctionExpr& expr) + : expr_(std::make_unique(expr)) { + } + + EvaluateResult Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const override; + + private: + std::unique_ptr expr_; +}; + +class CoreDivide : public EvaluableExpr { + public: + explicit CoreDivide(const api::FunctionExpr& expr) + : expr_(std::make_unique(expr)) { + } + + EvaluateResult Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const override; + + private: + std::unique_ptr expr_; +}; + +class CoreMod : public EvaluableExpr { + public: + explicit CoreMod(const api::FunctionExpr& expr) : expr_(std::make_unique(expr)) { } diff --git a/Firestore/core/src/model/value_util.cc b/Firestore/core/src/model/value_util.cc index 71ab44fd3f3..cb7c7f0d45e 100644 --- a/Firestore/core/src/model/value_util.cc +++ b/Firestore/core/src/model/value_util.cc @@ -1016,6 +1016,144 @@ Message DeepClone( return target; } +absl::optional GetInteger(const google_firestore_v1_Value& value) { + if (value.which_value_type == google_firestore_v1_Value_integer_value_tag) { + return value.integer_value; + } + return absl::nullopt; +} + +namespace { + +StrictEqualsResult StrictArrayEquals( + const google_firestore_v1_ArrayValue& left, + const google_firestore_v1_ArrayValue& right) { + if (left.values_count != right.values_count) { + return StrictEqualsResult::kNotEq; + } + + bool found_null = false; + for (pb_size_t i = 0; i < left.values_count; ++i) { + StrictEqualsResult element_result = + StrictEquals(left.values[i], right.values[i]); + switch (element_result) { + case StrictEqualsResult::kNotEq: + return StrictEqualsResult::kNotEq; + case StrictEqualsResult::kNull: + found_null = true; + break; + case StrictEqualsResult::kEq: + // Continue checking other elements + break; + } + } + + return found_null ? StrictEqualsResult::kNull : StrictEqualsResult::kEq; +} + +StrictEqualsResult StrictMapEquals(const google_firestore_v1_MapValue& left, + const google_firestore_v1_MapValue& right) { + if (left.fields_count != right.fields_count) { + return StrictEqualsResult::kNotEq; + } + + // Sort copies to compare map content regardless of original order. + auto left_map = DeepClone(left); + auto right_map = DeepClone(right); + SortFields(*left_map); + SortFields(*right_map); + + bool found_null = false; + for (pb_size_t i = 0; i < left_map->fields_count; ++i) { + // Compare keys first + if (nanopb::MakeStringView(left_map->fields[i].key) != + nanopb::MakeStringView(right_map->fields[i].key)) { + return StrictEqualsResult::kNotEq; + } + + // Compare values recursively + StrictEqualsResult value_result = + StrictEquals(left_map->fields[i].value, right_map->fields[i].value); + switch (value_result) { + case StrictEqualsResult::kNotEq: + return StrictEqualsResult::kNotEq; + case StrictEqualsResult::kNull: + found_null = true; + break; + case StrictEqualsResult::kEq: + // Continue checking other fields + break; + } + } + + return found_null ? StrictEqualsResult::kNull : StrictEqualsResult::kEq; +} + +StrictEqualsResult StrictNumberEquals(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) { + // Case 1: Both are longs + return left.integer_value == right.integer_value + ? StrictEqualsResult::kEq + : StrictEqualsResult::kNotEq; + } else if (left.which_value_type == + google_firestore_v1_Value_double_value_tag && + right.which_value_type == + google_firestore_v1_Value_double_value_tag) { + // Case 2: Both are doubles + // Standard double comparison handles 0.0 == -0.0 and NaN != NaN. + return left.double_value == right.double_value ? StrictEqualsResult::kEq + : StrictEqualsResult::kNotEq; + } else { + // Case 3: Mixed integer and double + // Promote integer to double for comparison. + double left_double = + (left.which_value_type == google_firestore_v1_Value_integer_value_tag) + ? static_cast(left.integer_value) + : left.double_value; + double right_double = + (right.which_value_type == google_firestore_v1_Value_integer_value_tag) + ? static_cast(right.integer_value) + : right.double_value; + return left_double == right_double ? StrictEqualsResult::kEq + : StrictEqualsResult::kNotEq; + } +} + +} // namespace + +StrictEqualsResult StrictEquals(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right) { + if (IsNullValue(left) || IsNullValue(right)) { + return StrictEqualsResult::kNull; + } + + TypeOrder left_type = GetTypeOrder(left); + TypeOrder right_type = GetTypeOrder(right); + if (left_type != right_type) { + return StrictEqualsResult::kNotEq; + } + + switch (left_type) { + case TypeOrder::kNumber: + return StrictNumberEquals(left, right); + case TypeOrder::kArray: + return StrictArrayEquals(left.array_value, right.array_value); + case TypeOrder::kVector: + case TypeOrder::kMap: + // Note: MaxValue is also a map, but should be handled by TypeOrder check + // if compared against a non-MaxValue. MaxValue == MaxValue is handled + // by the Equals call below. Vector equality is map equality. + return StrictMapEquals(left.map_value, right.map_value); + default: + // For all other types (Null, Boolean, Number, Timestamp, String, Blob, + // Ref, GeoPoint, MaxValue), the standard Equals function works. + return Equals(left, right) ? StrictEqualsResult::kEq + : StrictEqualsResult::kNotEq; + } +} + } // namespace model } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/model/value_util.h b/Firestore/core/src/model/value_util.h index d572e1489f7..4991acfbc58 100644 --- a/Firestore/core/src/model/value_util.h +++ b/Firestore/core/src/model/value_util.h @@ -77,6 +77,9 @@ enum class TypeOrder { kMaxValue = 12 }; +/** Result type for StrictEquals comparison. */ +enum class StrictEqualsResult { kEq, kNotEq, kNull }; + /** Returns the backend's type order of the given Value type. */ TypeOrder GetTypeOrder(const google_firestore_v1_Value& value); @@ -103,6 +106,15 @@ bool Equals(const google_firestore_v1_Value& left, bool Equals(const google_firestore_v1_ArrayValue& left, const google_firestore_v1_ArrayValue& right); +/** + * Performs a strict equality comparison used in Pipeline expressions + * evaluations. The main difference to Equals is its handling of null + * propagation, and it uses direct double value comparison (as opposed to Equals + * which use bits comparison). + */ +StrictEqualsResult StrictEquals(const google_firestore_v1_Value& left, + const google_firestore_v1_Value& right); + /** * Generates the canonical ID for the provided field value (as used in Target * serialization). @@ -277,6 +289,12 @@ inline bool IsMap(const absl::optional& value) { value->which_value_type == google_firestore_v1_Value_map_value_tag; } +/** + * Extracts the integer value if the input is an integer type. + * Returns nullopt otherwise. + */ +absl::optional GetInteger(const google_firestore_v1_Value& value); + } // namespace model inline bool operator==(const google_firestore_v1_Value& lhs, diff --git a/Firestore/core/test/unit/core/expressions/arithmetic_test.cc b/Firestore/core/test/unit/core/expressions/arithmetic_test.cc new file mode 100644 index 00000000000..c67c4c27b00 --- /dev/null +++ b/Firestore/core/test/unit/core/expressions/arithmetic_test.cc @@ -0,0 +1,832 @@ +/* + * Copyright 2025 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/core/expressions_eval.h" + +#include +#include +#include +#include +#include +#include + +#include "Firestore/core/test/unit/testutil/expression_test_util.h" +#include "Firestore/core/test/unit/testutil/testutil.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace core { + +using api::Expr; +using model::MutableDocument; // Used as PipelineInputOutput alias +using testing::_; +using testutil::AddExpr; +using testutil::DivideExpr; +using testutil::EvaluateExpr; +using testutil::ModExpr; +using testutil::MultiplyExpr; +using testutil::Returns; +using testutil::ReturnsError; +using testutil::SharedConstant; +using testutil::SubtractExpr; +using testutil::Value; + +// Base fixture for common setup (if needed later) +class ArithmeticExpressionsTest : public ::testing::Test {}; + +// Fixture for Add function tests +class AddFunctionTest : public ArithmeticExpressionsTest {}; + +// Fixture for Subtract function tests +class SubtractFunctionTest : public ArithmeticExpressionsTest {}; + +// Fixture for Multiply function tests +class MultiplyFunctionTest : public ArithmeticExpressionsTest {}; + +// Fixture for Divide function tests +class DivideFunctionTest : public ArithmeticExpressionsTest {}; + +// Fixture for Mod function tests +class ModFunctionTest : public ArithmeticExpressionsTest {}; + +// --- Add Tests --- + +TEST_F(AddFunctionTest, BasicNumerics) { + EXPECT_THAT( + EvaluateExpr(*AddExpr({SharedConstant(1LL), SharedConstant(2LL)})), + Returns(Value(3LL))); + EXPECT_THAT( + EvaluateExpr(*AddExpr({SharedConstant(1LL), SharedConstant(2.5)})), + Returns(Value(3.5))); + EXPECT_THAT( + EvaluateExpr(*AddExpr({SharedConstant(1.0), SharedConstant(2LL)})), + Returns(Value(3.0))); + EXPECT_THAT( + EvaluateExpr(*AddExpr({SharedConstant(1.0), SharedConstant(2.0)})), + Returns(Value(3.0))); +} + +TEST_F(AddFunctionTest, BasicNonNumerics) { + EXPECT_THAT( + EvaluateExpr(*AddExpr({SharedConstant(1LL), SharedConstant("1")})), + ReturnsError()); + EXPECT_THAT( + EvaluateExpr(*AddExpr({SharedConstant("1"), SharedConstant(1.0)})), + ReturnsError()); + EXPECT_THAT( + EvaluateExpr(*AddExpr({SharedConstant("1"), SharedConstant("1")})), + ReturnsError()); +} + +TEST_F(AddFunctionTest, DoubleLongAdditionOverflow) { + // Note: C++ double can represent Long.MAX_VALUE + 1.0 exactly, unlike some JS + // representations. + EXPECT_THAT(EvaluateExpr(*AddExpr({SharedConstant(9223372036854775807LL), + SharedConstant(1.0)})), + Returns(Value(9.223372036854776e+18))); + EXPECT_THAT(EvaluateExpr(*AddExpr({SharedConstant(9.223372036854776e+18), + SharedConstant(100LL)})), + Returns(Value(9.223372036854776e+18 + 100.0))); +} + +TEST_F(AddFunctionTest, DoubleAdditionOverflow) { + EXPECT_THAT(EvaluateExpr(*AddExpr( + {SharedConstant(std::numeric_limits::max()), + SharedConstant(std::numeric_limits::max())})), + Returns(Value(std::numeric_limits::infinity()))); + EXPECT_THAT(EvaluateExpr(*AddExpr( + {SharedConstant(-std::numeric_limits::max()), + SharedConstant(-std::numeric_limits::max())})), + Returns(Value(-std::numeric_limits::infinity()))); +} + +TEST_F(AddFunctionTest, SumPosAndNegInfinityReturnNaN) { + EXPECT_THAT(EvaluateExpr(*AddExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(-std::numeric_limits::infinity())})), + Returns(Value(std::numeric_limits::quiet_NaN()))); +} + +TEST_F(AddFunctionTest, LongAdditionOverflow) { + EXPECT_THAT(EvaluateExpr( + *AddExpr({SharedConstant(std::numeric_limits::max()), + SharedConstant(1LL)})), + ReturnsError()); // Expect error due to overflow + EXPECT_THAT(EvaluateExpr( + *AddExpr({SharedConstant(std::numeric_limits::min()), + SharedConstant(-1LL)})), + ReturnsError()); // Expect error due to overflow + EXPECT_THAT(EvaluateExpr(*AddExpr( + {SharedConstant(1LL), + SharedConstant(std::numeric_limits::max())})), + ReturnsError()); // Expect error due to overflow +} + +TEST_F(AddFunctionTest, NanNumberReturnNaN) { + double nan_val = std::numeric_limits::quiet_NaN(); + EXPECT_THAT( + EvaluateExpr(*AddExpr({SharedConstant(1LL), SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT( + EvaluateExpr(*AddExpr({SharedConstant(1.0), SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*AddExpr({SharedConstant(9007199254740991LL), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*AddExpr({SharedConstant(-9007199254740991LL), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT( + EvaluateExpr(*AddExpr({SharedConstant(std::numeric_limits::max()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*AddExpr( + {SharedConstant(std::numeric_limits::lowest()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*AddExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*AddExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); +} + +TEST_F(AddFunctionTest, NanNotNumberTypeReturnError) { + EXPECT_THAT(EvaluateExpr(*AddExpr( + {SharedConstant(std::numeric_limits::quiet_NaN()), + SharedConstant("hello world")})), + ReturnsError()); +} + +TEST_F(AddFunctionTest, MultiArgument) { + // EvaluateExpr handles single expression, so nest calls for multi-arg + auto add12 = AddExpr({SharedConstant(1LL), SharedConstant(2LL)}); + EXPECT_THAT(EvaluateExpr(*AddExpr({add12, SharedConstant(3LL)})), + Returns(Value(6LL))); + + auto add10_2 = AddExpr({SharedConstant(1.0), SharedConstant(2LL)}); + EXPECT_THAT(EvaluateExpr(*AddExpr({add10_2, SharedConstant(3LL)})), + Returns(Value(6.0))); +} + +// --- Subtract Tests --- + +TEST_F(SubtractFunctionTest, BasicNumerics) { + EXPECT_THAT( + EvaluateExpr(*SubtractExpr({SharedConstant(1LL), SharedConstant(2LL)})), + Returns(Value(-1LL))); + EXPECT_THAT( + EvaluateExpr(*SubtractExpr({SharedConstant(1LL), SharedConstant(2.5)})), + Returns(Value(-1.5))); + EXPECT_THAT( + EvaluateExpr(*SubtractExpr({SharedConstant(1.0), SharedConstant(2LL)})), + Returns(Value(-1.0))); + EXPECT_THAT( + EvaluateExpr(*SubtractExpr({SharedConstant(1.0), SharedConstant(2.0)})), + Returns(Value(-1.0))); +} + +TEST_F(SubtractFunctionTest, BasicNonNumerics) { + EXPECT_THAT( + EvaluateExpr(*SubtractExpr({SharedConstant(1LL), SharedConstant("1")})), + ReturnsError()); + EXPECT_THAT( + EvaluateExpr(*SubtractExpr({SharedConstant("1"), SharedConstant(1.0)})), + ReturnsError()); + EXPECT_THAT( + EvaluateExpr(*SubtractExpr({SharedConstant("1"), SharedConstant("1")})), + ReturnsError()); +} + +TEST_F(SubtractFunctionTest, DoubleSubtractionOverflow) { + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(-std::numeric_limits::max()), + SharedConstant(std::numeric_limits::max())})), + Returns(Value(-std::numeric_limits::infinity()))); + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(std::numeric_limits::max()), + SharedConstant(-std::numeric_limits::max())})), + Returns(Value(std::numeric_limits::infinity()))); +} + +TEST_F(SubtractFunctionTest, LongSubtractionOverflow) { + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(std::numeric_limits::min()), + SharedConstant(1LL)})), + ReturnsError()); + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(std::numeric_limits::max()), + SharedConstant(-1LL)})), + ReturnsError()); +} + +TEST_F(SubtractFunctionTest, NanNumberReturnNaN) { + double nan_val = std::numeric_limits::quiet_NaN(); + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(1LL), SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(1.0), SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*SubtractExpr({SharedConstant(9007199254740991LL), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*SubtractExpr({SharedConstant(-9007199254740991LL), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(std::numeric_limits::max()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(std::numeric_limits::lowest()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); +} + +TEST_F(SubtractFunctionTest, NanNotNumberTypeReturnError) { + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(std::numeric_limits::quiet_NaN()), + SharedConstant("hello world")})), + ReturnsError()); +} + +TEST_F(SubtractFunctionTest, PositiveInfinity) { + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(1LL)})), + Returns(Value(std::numeric_limits::infinity()))); + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(1LL), + SharedConstant(std::numeric_limits::infinity())})), + Returns(Value(-std::numeric_limits::infinity()))); +} + +TEST_F(SubtractFunctionTest, NegativeInfinity) { + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(1LL)})), + Returns(Value(-std::numeric_limits::infinity()))); + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(1LL), + SharedConstant(-std::numeric_limits::infinity())})), + Returns(Value(std::numeric_limits::infinity()))); +} + +TEST_F(SubtractFunctionTest, PositiveInfinityNegativeInfinity) { + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(-std::numeric_limits::infinity())})), + Returns(Value(std::numeric_limits::infinity()))); + EXPECT_THAT(EvaluateExpr(*SubtractExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(std::numeric_limits::infinity())})), + Returns(Value(-std::numeric_limits::infinity()))); +} + +// --- Multiply Tests --- + +TEST_F(MultiplyFunctionTest, BasicNumerics) { + EXPECT_THAT( + EvaluateExpr(*MultiplyExpr({SharedConstant(1LL), SharedConstant(2LL)})), + Returns(Value(2LL))); + EXPECT_THAT( + EvaluateExpr(*MultiplyExpr({SharedConstant(3LL), SharedConstant(2.5)})), + Returns(Value(7.5))); + EXPECT_THAT( + EvaluateExpr(*MultiplyExpr({SharedConstant(1.0), SharedConstant(2LL)})), + Returns(Value(2.0))); + EXPECT_THAT( + EvaluateExpr(*MultiplyExpr({SharedConstant(1.32), SharedConstant(2.0)})), + Returns(Value(2.64))); +} + +TEST_F(MultiplyFunctionTest, BasicNonNumerics) { + EXPECT_THAT( + EvaluateExpr(*MultiplyExpr({SharedConstant(1LL), SharedConstant("1")})), + ReturnsError()); + EXPECT_THAT( + EvaluateExpr(*MultiplyExpr({SharedConstant("1"), SharedConstant(1.0)})), + ReturnsError()); + EXPECT_THAT( + EvaluateExpr(*MultiplyExpr({SharedConstant("1"), SharedConstant("1")})), + ReturnsError()); +} + +TEST_F(MultiplyFunctionTest, DoubleLongMultiplicationOverflow) { + // C++ double handles this fine + EXPECT_THAT(EvaluateExpr(*MultiplyExpr({SharedConstant(9223372036854775807LL), + SharedConstant(100.0)})), + Returns(Value(9.223372036854776e+20))); // Approx + EXPECT_THAT(EvaluateExpr(*MultiplyExpr({SharedConstant(9223372036854775807LL), + SharedConstant(100LL)})), + ReturnsError()); // Integer overflow +} + +TEST_F(MultiplyFunctionTest, DoubleMultiplicationOverflow) { + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(std::numeric_limits::max()), + SharedConstant(std::numeric_limits::max())})), + Returns(Value(std::numeric_limits::infinity()))); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(-std::numeric_limits::max()), + SharedConstant(std::numeric_limits::max())})), + Returns(Value(-std::numeric_limits::infinity()))); +} + +TEST_F(MultiplyFunctionTest, LongMultiplicationOverflow) { + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(std::numeric_limits::max()), + SharedConstant(10LL)})), + ReturnsError()); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(std::numeric_limits::min()), + SharedConstant(10LL)})), + ReturnsError()); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(-10LL), + SharedConstant(std::numeric_limits::max())})), + ReturnsError()); + // Note: min * -10 overflows + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(-10LL), + SharedConstant(std::numeric_limits::min())})), + ReturnsError()); +} + +TEST_F(MultiplyFunctionTest, NanNumberReturnNaN) { + double nan_val = std::numeric_limits::quiet_NaN(); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(1LL), SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(1.0), SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr({SharedConstant(9007199254740991LL), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr({SharedConstant(-9007199254740991LL), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(std::numeric_limits::max()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(std::numeric_limits::lowest()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); +} + +TEST_F(MultiplyFunctionTest, NanNotNumberTypeReturnError) { + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(std::numeric_limits::quiet_NaN()), + SharedConstant("hello world")})), + ReturnsError()); +} + +TEST_F(MultiplyFunctionTest, PositiveInfinity) { + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(1LL)})), + Returns(Value(std::numeric_limits::infinity()))); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(1LL), + SharedConstant(std::numeric_limits::infinity())})), + Returns(Value(std::numeric_limits::infinity()))); +} + +TEST_F(MultiplyFunctionTest, NegativeInfinity) { + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(1LL)})), + Returns(Value(-std::numeric_limits::infinity()))); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(1LL), + SharedConstant(-std::numeric_limits::infinity())})), + Returns(Value(-std::numeric_limits::infinity()))); +} + +TEST_F(MultiplyFunctionTest, + PositiveInfinityNegativeInfinityReturnsNegativeInfinity) { + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(-std::numeric_limits::infinity())})), + Returns(Value(-std::numeric_limits::infinity()))); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(std::numeric_limits::infinity())})), + Returns(Value(-std::numeric_limits::infinity()))); +} + +TEST_F(MultiplyFunctionTest, MultiArgument) { + auto mult12 = MultiplyExpr({SharedConstant(1LL), SharedConstant(2LL)}); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr({mult12, SharedConstant(3LL)})), + Returns(Value(6LL))); + + auto mult23 = MultiplyExpr({SharedConstant(2LL), SharedConstant(3LL)}); + EXPECT_THAT(EvaluateExpr(*MultiplyExpr({SharedConstant(1.0), mult23})), + Returns(Value(6.0))); +} + +// --- Divide Tests --- + +TEST_F(DivideFunctionTest, BasicNumerics) { + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(10LL), SharedConstant(2LL)})), + Returns(Value(5LL))); + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(10LL), SharedConstant(2.0)})), + Returns(Value(5.0))); + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(10.0), SharedConstant(3LL)})), + Returns(Value(10.0 / 3.0))); + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(10.0), SharedConstant(7.0)})), + Returns(Value(10.0 / 7.0))); +} + +TEST_F(DivideFunctionTest, BasicNonNumerics) { + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(1LL), SharedConstant("1")})), + ReturnsError()); + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant("1"), SharedConstant(1.0)})), + ReturnsError()); + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant("1"), SharedConstant("1")})), + ReturnsError()); +} + +TEST_F(DivideFunctionTest, LongDivision) { + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(10LL), SharedConstant(3LL)})), + Returns(Value(3LL))); // Integer division + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(-10LL), SharedConstant(3LL)})), + Returns(Value(-3LL))); // Integer division + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(10LL), SharedConstant(-3LL)})), + Returns(Value(-3LL))); // Integer division + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(-10LL), SharedConstant(-3LL)})), + Returns(Value(3LL))); // Integer division +} + +TEST_F(DivideFunctionTest, DoubleDivisionOverflow) { + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(std::numeric_limits::max()), + SharedConstant(0.5)})), // Multiplying by 2 essentially + Returns(Value(std::numeric_limits::infinity()))); + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(-std::numeric_limits::max()), + SharedConstant(0.5)})), + Returns(Value(-std::numeric_limits::infinity()))); +} + +TEST_F(DivideFunctionTest, ByZero) { + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(1LL), SharedConstant(0LL)})), + ReturnsError()); // Integer division by zero is error + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(1.1), SharedConstant(0.0)})), + Returns(Value(std::numeric_limits::infinity()))); + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(1.1), SharedConstant(-0.0)})), + Returns(Value(-std::numeric_limits::infinity()))); + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(0.0), SharedConstant(0.0)})), + Returns(Value(std::numeric_limits::quiet_NaN()))); +} + +TEST_F(DivideFunctionTest, NanNumberReturnNaN) { + double nan_val = std::numeric_limits::quiet_NaN(); + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(1LL), SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(nan_val), SharedConstant(1LL)})), + Returns(Value(nan_val))); + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(1.0), SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT( + EvaluateExpr(*DivideExpr({SharedConstant(nan_val), SharedConstant(1.0)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(nan_val), SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(nan_val), + SharedConstant(-std::numeric_limits::infinity())})), + Returns(Value(nan_val))); +} + +TEST_F(DivideFunctionTest, NanNotNumberTypeReturnError) { + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(std::numeric_limits::quiet_NaN()), + SharedConstant("hello world")})), + ReturnsError()); +} + +TEST_F(DivideFunctionTest, PositiveInfinity) { + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(1LL)})), + Returns(Value(std::numeric_limits::infinity()))); + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(1LL), + SharedConstant(std::numeric_limits::infinity())})), + Returns(Value(0.0))); +} + +TEST_F(DivideFunctionTest, NegativeInfinity) { + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(1LL)})), + Returns(Value(-std::numeric_limits::infinity()))); + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(1LL), + SharedConstant(-std::numeric_limits::infinity())})), + Returns(Value(-0.0))); // Note: -0.0 +} + +TEST_F(DivideFunctionTest, PositiveInfinityNegativeInfinityReturnsNan) { + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(-std::numeric_limits::infinity())})), + Returns(Value(std::numeric_limits::quiet_NaN()))); + EXPECT_THAT(EvaluateExpr(*DivideExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(std::numeric_limits::infinity())})), + Returns(Value(std::numeric_limits::quiet_NaN()))); +} + +// --- Mod Tests --- + +TEST_F(ModFunctionTest, DivisorZeroThrowsError) { + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(42LL), SharedConstant(0LL)})), + ReturnsError()); + // Note: C++ doesn't distinguish -0LL from 0LL + // EXPECT_TRUE(AssertResultEquals( + // EvaluateExpr(*ModExpr({SharedConstant(42LL), SharedConstant(-0LL)})), + // EvaluateResult::NewError())); + + // Double modulo by zero returns NaN in our implementation (matching JS %) + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(42.0), SharedConstant(0.0)})), + Returns(Value(std::numeric_limits::quiet_NaN()))); + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(42.0), SharedConstant(-0.0)})), + Returns(Value(std::numeric_limits::quiet_NaN()))); +} + +TEST_F(ModFunctionTest, DividendZeroReturnsZero) { + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(0LL), SharedConstant(42LL)})), + Returns(Value(0LL))); + // Note: C++ doesn't distinguish -0LL from 0LL + // EXPECT_THAT( + // EvaluateExpr(*ModExpr({SharedConstant(-0LL), SharedConstant(42LL)})), + // Returns(Value(0LL))); + + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(0.0), SharedConstant(42.0)})), + Returns(Value(0.0))); + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(-0.0), SharedConstant(42.0)})), + Returns(Value(-0.0))); +} + +TEST_F(ModFunctionTest, LongPositivePositive) { + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(10LL), SharedConstant(3LL)})), + Returns(Value(1LL))); +} + +TEST_F(ModFunctionTest, LongNegativeNegative) { + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(-10LL), SharedConstant(-3LL)})), + Returns(Value(-1LL))); // C++ % behavior +} + +TEST_F(ModFunctionTest, LongPositiveNegative) { + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(10LL), SharedConstant(-3LL)})), + Returns(Value(1LL))); // C++ % behavior +} + +TEST_F(ModFunctionTest, LongNegativePositive) { + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(-10LL), SharedConstant(3LL)})), + Returns(Value(-1LL))); // C++ % behavior +} + +TEST_F(ModFunctionTest, DoublePositivePositive) { + auto result = + EvaluateExpr(*ModExpr({SharedConstant(10.5), SharedConstant(3.0)})); + EXPECT_EQ(result.type(), EvaluateResult::ResultType::kDouble); + EXPECT_NEAR(result.value()->double_value, 1.5, 1e-9); +} + +TEST_F(ModFunctionTest, DoubleNegativeNegative) { + auto result = + EvaluateExpr(*ModExpr({SharedConstant(-7.3), SharedConstant(-1.8)})); + EXPECT_EQ(result.type(), EvaluateResult::ResultType::kDouble); + EXPECT_NEAR(result.value()->double_value, -0.1, 1e-9); // std::fmod behavior +} + +TEST_F(ModFunctionTest, DoublePositiveNegative) { + auto result = + EvaluateExpr(*ModExpr({SharedConstant(9.8), SharedConstant(-2.5)})); + EXPECT_EQ(result.type(), EvaluateResult::ResultType::kDouble); + EXPECT_NEAR(result.value()->double_value, 2.3, 1e-9); // std::fmod behavior +} + +TEST_F(ModFunctionTest, DoubleNegativePositive) { + auto result = + EvaluateExpr(*ModExpr({SharedConstant(-7.5), SharedConstant(2.3)})); + EXPECT_EQ(result.type(), EvaluateResult::ResultType::kDouble); + EXPECT_NEAR(result.value()->double_value, -0.6, 1e-9); // std::fmod behavior +} + +TEST_F(ModFunctionTest, LongPerfectlyDivisible) { + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(10LL), SharedConstant(5LL)})), + Returns(Value(0LL))); + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(-10LL), SharedConstant(5LL)})), + Returns(Value(0LL))); + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(10LL), SharedConstant(-5LL)})), + Returns(Value(0LL))); + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(-10LL), SharedConstant(-5LL)})), + Returns(Value(0LL))); +} + +TEST_F(ModFunctionTest, DoublePerfectlyDivisible) { + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(10.0), SharedConstant(2.5)})), + Returns(Value(0.0))); + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(10.0), SharedConstant(-2.5)})), + Returns(Value(0.0))); + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(-10.0), SharedConstant(2.5)})), + Returns(Value(-0.0))); + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(-10.0), SharedConstant(-2.5)})), + Returns(Value(-0.0))); +} + +TEST_F(ModFunctionTest, NonNumericsReturnError) { + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(10LL), SharedConstant("1")})), + ReturnsError()); + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant("1"), SharedConstant(10LL)})), + ReturnsError()); + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant("1"), SharedConstant("1")})), + ReturnsError()); +} + +TEST_F(ModFunctionTest, NanNumberReturnNaN) { + double nan_val = std::numeric_limits::quiet_NaN(); + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(1LL), SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT( + EvaluateExpr(*ModExpr({SharedConstant(1.0), SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(nan_val)})), + Returns(Value(nan_val))); +} + +TEST_F(ModFunctionTest, NanNotNumberTypeReturnError) { + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(std::numeric_limits::quiet_NaN()), + SharedConstant("hello world")})), + ReturnsError()); +} + +TEST_F(ModFunctionTest, NumberPosInfinityReturnSelf) { + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(1LL), + SharedConstant(std::numeric_limits::infinity())})), + Returns(Value(1.0))); // fmod(1, inf) -> 1 + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(42.123), + SharedConstant(std::numeric_limits::infinity())})), + Returns(Value(42.123))); + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(-99.9), + SharedConstant(std::numeric_limits::infinity())})), + Returns(Value(-99.9))); +} + +TEST_F(ModFunctionTest, PosInfinityNumberReturnNaN) { + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(1LL)})), + Returns(Value(std::numeric_limits::quiet_NaN()))); + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(42.123)})), + Returns(Value(std::numeric_limits::quiet_NaN()))); + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(-99.9)})), + Returns(Value(std::numeric_limits::quiet_NaN()))); +} + +TEST_F(ModFunctionTest, NumberNegInfinityReturnSelf) { + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(1LL), + SharedConstant(-std::numeric_limits::infinity())})), + Returns(Value(1.0))); // fmod(1, -inf) -> 1 + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(42.123), + SharedConstant(-std::numeric_limits::infinity())})), + Returns(Value(42.123))); + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(-99.9), + SharedConstant(-std::numeric_limits::infinity())})), + Returns(Value(-99.9))); +} + +TEST_F(ModFunctionTest, NegInfinityNumberReturnNaN) { + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(1LL)})), + Returns(Value(std::numeric_limits::quiet_NaN()))); + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(42.123)})), + Returns(Value(std::numeric_limits::quiet_NaN()))); + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(-99.9)})), + Returns(Value(std::numeric_limits::quiet_NaN()))); +} + +TEST_F(ModFunctionTest, PosAndNegInfinityReturnNaN) { + EXPECT_THAT(EvaluateExpr(*ModExpr( + {SharedConstant(std::numeric_limits::infinity()), + SharedConstant(-std::numeric_limits::infinity())})), + Returns(Value(std::numeric_limits::quiet_NaN()))); +} + +} // namespace core +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/core/expressions/comparison_test.cc b/Firestore/core/test/unit/core/expressions/comparison_test.cc index 6b3fe1d179e..5ea9c40fa96 100644 --- a/Firestore/core/test/unit/core/expressions/comparison_test.cc +++ b/Firestore/core/test/unit/core/expressions/comparison_test.cc @@ -14,60 +14,921 @@ * limitations under the License. */ +#include "Firestore/core/src/core/expressions_eval.h" // For EvaluateResult, CoreEq etc. + +#include +#include #include +#include #include +#include -#include "Firestore/core/src/api/expressions.h" -#include "Firestore/core/src/api/stages.h" -#include "Firestore/core/src/core/expressions_eval.h" -#include "Firestore/core/src/model/database_id.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/test/unit/testutil/testutil.h" -#include "google/firestore/v1/document.nanopb.h" - +#include "Firestore/core/src/api/expressions.h" // Include for api::Constant, api::Field +#include "Firestore/core/src/model/database_id.h" // For DatabaseId +#include "Firestore/core/src/model/document_key.h" // For DocumentKey +#include "Firestore/core/src/model/value_util.h" // For value constants like NaNValue, TypeOrder, NullValue, CanonicalId, Equals +#include "Firestore/core/test/unit/testutil/expression_test_util.h" // For EvaluateExpr, EqExpr, ComparisonValueTestData, RefConstant etc. +#include "Firestore/core/test/unit/testutil/testutil.h" // For test helpers like Value, Array, Map, BlobValue, Doc +#include "gmock/gmock.h" #include "gtest/gtest.h" namespace firebase { namespace firestore { +namespace core { + +using api::Expr; +using model::DatabaseId; +using model::DocumentKey; +using model::MutableDocument; // Used as PipelineInputOutput alias +using testing::_; +// Explicitly qualify testutil helpers to avoid ambiguity +using testutil::ComparisonValueTestData; +using testutil::EqExpr; +using testutil::EvaluateExpr; +using testutil::GteExpr; +using testutil::GtExpr; +using testutil::LteExpr; +using testutil::LtExpr; +using testutil::NeqExpr; +using testutil::RefConstant; +using testutil::Returns; +using testutil::ReturnsError; +using testutil::ReturnsNull; +using testutil::ReturnsUnset; +using testutil::SharedConstant; + +// Base fixture for common setup +class ComparisonExpressionsTest : public ::testing::Test { + protected: + // Helper moved to expression_test_util.h +}; -namespace { +// Fixture for Eq function tests +class EqFunctionTest : public ComparisonExpressionsTest {}; -template -api::FunctionExpr eq(T lhs, Q rhs) { - return api::FunctionExpr( - "eq", {std::make_shared(lhs), std::make_shared(rhs)}); +// Helper to get canonical ID for logging, handling potential non-constant exprs +std::string ExprId(const std::shared_ptr& expr) { + if (auto constant = std::dynamic_pointer_cast(expr)) { + // Try accessing the underlying proto message via proto() + return model::CanonicalId(constant->to_proto()); + } else if (auto field = std::dynamic_pointer_cast(expr)) { + return "Field(" + field->field_path().CanonicalString() + ")"; + } + return ""; } -api::Constant constant(int value) { - google_firestore_v1_Value result; - result.which_value_type = google_firestore_v1_Value_integer_value_tag; - result.integer_value = value; - return api::Constant(nanopb::MakeSharedMessage(std::move(result))); +TEST_F(EqFunctionTest, EquivalentValuesReturnTrue) { + for (const auto& pair : ComparisonValueTestData::EquivalentValues()) { + EXPECT_THAT(EvaluateExpr(*EqExpr({pair.first, pair.second})), + Returns(testutil::Value(true))) + << "eq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } } -remote::Serializer serializer(model::DatabaseId("test-project")); +TEST_F(EqFunctionTest, LessThanValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::LessThanValues()) { + EXPECT_THAT(EvaluateExpr(*EqExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "eq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(EqFunctionTest, GreaterThanValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::GreaterThanValues()) { + EXPECT_THAT(EvaluateExpr(*EqExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "eq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} -api::EvaluateContext NewContext() { - return {&serializer}; +TEST_F(EqFunctionTest, MixedTypeValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::MixedTypeValues()) { + EXPECT_THAT(EvaluateExpr(*EqExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "eq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } } -} // namespace +// --- Specific Eq Tests (Null, NaN, Missing, Error) --- -namespace core { +// Fixture for Neq function tests +class NeqFunctionTest : public ComparisonExpressionsTest {}; + +// Fixture for Lt function tests +class LtFunctionTest : public ComparisonExpressionsTest {}; + +// Fixture for Lte function tests +class LteFunctionTest : public ComparisonExpressionsTest {}; + +// Fixture for Gt function tests +class GtFunctionTest : public ComparisonExpressionsTest {}; + +// Fixture for Gte function tests +class GteFunctionTest : public ComparisonExpressionsTest {}; + +// --- Eq (==) Tests --- + +TEST_F(EqFunctionTest, NullEqualsNullReturnsNull) { + EXPECT_THAT(EvaluateExpr(*EqExpr({SharedConstant(model::NullValue()), + SharedConstant(model::NullValue())})), + ReturnsNull()); +} + +// Corresponds to eq.null_any_returnsNull in typescript +TEST_F(EqFunctionTest, NullOperandReturnsNull) { + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT( + EvaluateExpr(*EqExpr({SharedConstant(model::NullValue()), val})), + ReturnsNull()) + << "eq(null, " << ExprId(val) << ")"; + EXPECT_THAT( + EvaluateExpr(*EqExpr({val, SharedConstant(model::NullValue())})), + ReturnsNull()) + << "eq(" << ExprId(val) << ", null)"; + } + EXPECT_THAT( + EvaluateExpr(*EqExpr({SharedConstant(model::NullValue()), + std::make_shared("nonexistent")})), + ReturnsUnset()); +} + +// Corresponds to eq.nan tests in typescript +TEST_F(EqFunctionTest, NaNComparisonsReturnFalse) { + auto nan_expr = SharedConstant(std::numeric_limits::quiet_NaN()); + EXPECT_THAT(EvaluateExpr(*EqExpr({nan_expr, nan_expr})), + Returns(testutil::Value(false))); // NaN == NaN is false + + for (const auto& num_val : ComparisonValueTestData::NumericValues()) { + EXPECT_THAT(EvaluateExpr(*EqExpr({nan_expr, num_val})), + Returns(testutil::Value(false))) + << "eq(NaN, " << ExprId(num_val) << ")"; + EXPECT_THAT(EvaluateExpr(*EqExpr({num_val, nan_expr})), + Returns(testutil::Value(false))) + << "eq(" << ExprId(num_val) << ", NaN)"; + } + + for (const auto& other_val : + ComparisonValueTestData::AllSupportedComparableValues()) { + bool is_numeric = false; + for (const auto& num_val : ComparisonValueTestData::NumericValues()) { + if (other_val == num_val) { + is_numeric = true; + break; + } + } + if (!is_numeric) { + EXPECT_THAT(EvaluateExpr(*EqExpr({nan_expr, other_val})), + Returns(testutil::Value(false))) + << "eq(NaN, " << ExprId(other_val) << ")"; + EXPECT_THAT(EvaluateExpr(*EqExpr({other_val, nan_expr})), + Returns(testutil::Value(false))) + << "eq(" << ExprId(other_val) << ", NaN)"; + } + } + + EXPECT_THAT( + EvaluateExpr(*EqExpr({SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN()))), + SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN())))})), + Returns(testutil::Value(false))); + EXPECT_THAT( + EvaluateExpr(*EqExpr( + {SharedConstant(testutil::Map( + "foo", + testutil::Value(std::numeric_limits::quiet_NaN()))), + SharedConstant(testutil::Map( + "foo", + testutil::Value(std::numeric_limits::quiet_NaN())))})), + Returns(testutil::Value(false))); +} + +// Corresponds to eq.nullInArray_equality / eq.nullInMap_equality / +// eq.null_missingInMap_equality +TEST_F(EqFunctionTest, NullContainerEquality) { + auto null_array = SharedConstant(testutil::Array(testutil::Value(nullptr))); + EXPECT_THAT(EvaluateExpr(*EqExpr({null_array, SharedConstant(1LL)})), + Returns(testutil::Value(false))); + EXPECT_THAT(EvaluateExpr(*EqExpr({null_array, SharedConstant("1")})), + Returns(testutil::Value(false))); + EXPECT_THAT( + EvaluateExpr(*EqExpr({null_array, SharedConstant(model::NullValue())})), + ReturnsNull()); + EXPECT_THAT(EvaluateExpr(*EqExpr( + {null_array, + SharedConstant(std::numeric_limits::quiet_NaN())})), + Returns(testutil::Value(false))); + EXPECT_THAT( + EvaluateExpr(*EqExpr({null_array, SharedConstant(testutil::Array())})), + Returns(testutil::Value(false))); + EXPECT_THAT( + EvaluateExpr(*EqExpr( + {null_array, SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN())))})), + ReturnsNull()); + EXPECT_THAT( + EvaluateExpr(*EqExpr({null_array, SharedConstant(testutil::Array( + testutil::Value(nullptr)))})), + ReturnsNull()); + + auto null_map = + SharedConstant(testutil::Map("foo", testutil::Value(nullptr))); + EXPECT_THAT( + EvaluateExpr(*EqExpr({null_map, SharedConstant(testutil::Map( + "foo", testutil::Value(nullptr)))})), + ReturnsNull()); + EXPECT_THAT( + EvaluateExpr(*EqExpr({null_map, SharedConstant(testutil::Map())})), + Returns(testutil::Value(false))); +} + +// Corresponds to eq.error_ tests +TEST_F(EqFunctionTest, ErrorHandling) { + auto error_expr = std::make_shared("a.b"); + auto non_map_input = testutil::Doc("coll/doc", 1, testutil::Map("a", 123)); + + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT(EvaluateExpr(*EqExpr({error_expr, val}), non_map_input), + ReturnsUnset()); + EXPECT_THAT(EvaluateExpr(*EqExpr({val, error_expr}), non_map_input), + ReturnsUnset()); + } + EXPECT_THAT(EvaluateExpr(*EqExpr({error_expr, error_expr}), non_map_input), + ReturnsUnset()); + EXPECT_THAT( + EvaluateExpr(*EqExpr({error_expr, SharedConstant(model::NullValue())}), + non_map_input), + ReturnsUnset()); +} + +TEST_F(EqFunctionTest, MissingFieldReturnsUnset) { + EXPECT_THAT(EvaluateExpr(*EqExpr({std::make_shared("nonexistent"), + SharedConstant(testutil::Value(1LL))})), + ReturnsUnset()); + EXPECT_THAT( + EvaluateExpr(*EqExpr({SharedConstant(testutil::Value(1LL)), + std::make_shared("nonexistent")})), + ReturnsUnset()); +} + +// --- Neq (!=) Tests --- + +TEST_F(NeqFunctionTest, EquivalentValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::EquivalentValues()) { + EXPECT_THAT(EvaluateExpr(*NeqExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "neq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(NeqFunctionTest, LessThanValuesReturnTrue) { + for (const auto& pair : ComparisonValueTestData::LessThanValues()) { + EXPECT_THAT(EvaluateExpr(*NeqExpr({pair.first, pair.second})), + Returns(testutil::Value(true))) + << "neq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(NeqFunctionTest, GreaterThanValuesReturnTrue) { + for (const auto& pair : ComparisonValueTestData::GreaterThanValues()) { + EXPECT_THAT(EvaluateExpr(*NeqExpr({pair.first, pair.second})), + Returns(testutil::Value(true))) + << "neq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(NeqFunctionTest, MixedTypeValuesReturnTrue) { + for (const auto& pair : ComparisonValueTestData::MixedTypeValues()) { + EXPECT_THAT(EvaluateExpr(*NeqExpr({pair.first, pair.second})), + Returns(testutil::Value(true))) + << "neq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +// --- Specific Neq Tests --- + +TEST_F(NeqFunctionTest, NullNotEqualsNullReturnsNull) { + EXPECT_THAT(EvaluateExpr(*NeqExpr({SharedConstant(model::NullValue()), + SharedConstant(model::NullValue())})), + ReturnsNull()); +} + +// Corresponds to neq.null_any_returnsNull +TEST_F(NeqFunctionTest, NullOperandReturnsNull) { + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT( + EvaluateExpr(*NeqExpr({SharedConstant(model::NullValue()), val})), + ReturnsNull()) + << "neq(null, " << ExprId(val) << ")"; + EXPECT_THAT( + EvaluateExpr(*NeqExpr({val, SharedConstant(model::NullValue())})), + ReturnsNull()) + << "neq(" << ExprId(val) << ", null)"; + } + EXPECT_THAT( + EvaluateExpr(*NeqExpr({SharedConstant(model::NullValue()), + std::make_shared("nonexistent")})), + ReturnsUnset()); +} + +// Corresponds to neq.nan tests +TEST_F(NeqFunctionTest, NaNComparisonsReturnTrue) { + auto nan_expr = SharedConstant(std::numeric_limits::quiet_NaN()); + EXPECT_THAT(EvaluateExpr(*NeqExpr({nan_expr, nan_expr})), + Returns(testutil::Value(true))); // NaN != NaN is true + + for (const auto& num_val : ComparisonValueTestData::NumericValues()) { + EXPECT_THAT(EvaluateExpr(*NeqExpr({nan_expr, num_val})), + Returns(testutil::Value(true))) + << "neq(NaN, " << ExprId(num_val) << ")"; + EXPECT_THAT(EvaluateExpr(*NeqExpr({num_val, nan_expr})), + Returns(testutil::Value(true))) + << "neq(" << ExprId(num_val) << ", NaN)"; + } + + for (const auto& other_val : + ComparisonValueTestData::AllSupportedComparableValues()) { + bool is_numeric = false; + for (const auto& num_val : ComparisonValueTestData::NumericValues()) { + if (other_val == num_val) { + is_numeric = true; + break; + } + } + if (!is_numeric) { + EXPECT_THAT(EvaluateExpr(*NeqExpr({nan_expr, other_val})), + Returns(testutil::Value(true))) + << "neq(NaN, " << ExprId(other_val) << ")"; + EXPECT_THAT(EvaluateExpr(*NeqExpr({other_val, nan_expr})), + Returns(testutil::Value(true))) + << "neq(" << ExprId(other_val) << ", NaN)"; + } + } + + EXPECT_THAT( + EvaluateExpr(*NeqExpr({SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN()))), + SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN())))})), + Returns(testutil::Value(true))); + EXPECT_THAT( + EvaluateExpr(*NeqExpr( + {SharedConstant(testutil::Map( + "foo", + testutil::Value(std::numeric_limits::quiet_NaN()))), + SharedConstant(testutil::Map( + "foo", + testutil::Value(std::numeric_limits::quiet_NaN())))})), + Returns(testutil::Value(true))); +} -using testutil::Doc; -using testutil::Map; +// Corresponds to neq.error_ tests +TEST_F(NeqFunctionTest, ErrorHandling) { + auto error_expr = std::make_shared("a.b"); + auto non_map_input = testutil::Doc("coll/doc", 1, testutil::Map("a", 123)); + + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT(EvaluateExpr(*NeqExpr({error_expr, val}), non_map_input), + ReturnsUnset()); + EXPECT_THAT(EvaluateExpr(*NeqExpr({val, error_expr}), non_map_input), + ReturnsUnset()); + } + EXPECT_THAT(EvaluateExpr(*NeqExpr({error_expr, error_expr}), non_map_input), + ReturnsUnset()); + EXPECT_THAT( + EvaluateExpr(*NeqExpr({error_expr, SharedConstant(model::NullValue())}), + non_map_input), + ReturnsUnset()); +} + +TEST_F(NeqFunctionTest, MissingFieldReturnsUnset) { + EXPECT_THAT( + EvaluateExpr(*NeqExpr({std::make_shared("nonexistent"), + SharedConstant(testutil::Value(1LL))})), + ReturnsUnset()); + EXPECT_THAT( + EvaluateExpr(*NeqExpr({SharedConstant(testutil::Value(1LL)), + std::make_shared("nonexistent")})), + ReturnsUnset()); +} + +// --- Lt (<) Tests --- + +TEST_F(LtFunctionTest, EquivalentValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::EquivalentValues()) { + EXPECT_THAT(EvaluateExpr(*LtExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "lt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(LtFunctionTest, LessThanValuesReturnTrue) { + for (const auto& pair : ComparisonValueTestData::LessThanValues()) { + auto left_const = + std::dynamic_pointer_cast(pair.first); + auto right_const = + std::dynamic_pointer_cast(pair.second); + // Use model::Equals to check for non-equal comparable pairs + EXPECT_THAT(EvaluateExpr(*LtExpr({pair.first, pair.second})), + Returns(testutil::Value(true))) + << "lt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(LtFunctionTest, GreaterThanValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::GreaterThanValues()) { + EXPECT_THAT(EvaluateExpr(*LtExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "lt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(LtFunctionTest, MixedTypeValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::MixedTypeValues()) { + EXPECT_THAT(EvaluateExpr(*LtExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "lt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +// --- Specific Lt Tests --- + +TEST_F(LtFunctionTest, NullOperandReturnsNull) { + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT( + EvaluateExpr(*LtExpr({SharedConstant(model::NullValue()), val})), + ReturnsNull()) + << "lt(null, " << ExprId(val) << ")"; + EXPECT_THAT( + EvaluateExpr(*LtExpr({val, SharedConstant(model::NullValue())})), + ReturnsNull()) + << "lt(" << ExprId(val) << ", null)"; + } + EXPECT_THAT(EvaluateExpr(*LtExpr({SharedConstant(model::NullValue()), + SharedConstant(model::NullValue())})), + ReturnsNull()); + EXPECT_THAT( + EvaluateExpr(*LtExpr({SharedConstant(model::NullValue()), + std::make_shared("nonexistent")})), + ReturnsUnset()); +} -TEST(Eq, Basic) { - auto result = eq(api::Field("foo"), constant(42)) - .ToEvaluable() - ->Evaluate(NewContext(), Doc("docs/1", 0, Map("foo", 42))); +TEST_F(LtFunctionTest, NaNComparisonsReturnFalse) { + auto nan_expr = SharedConstant(std::numeric_limits::quiet_NaN()); + EXPECT_THAT(EvaluateExpr(*LtExpr({nan_expr, nan_expr})), + Returns(testutil::Value(false))); + + for (const auto& num_val : ComparisonValueTestData::NumericValues()) { + EXPECT_THAT(EvaluateExpr(*LtExpr({nan_expr, num_val})), + Returns(testutil::Value(false))) + << "lt(NaN, " << ExprId(num_val) << ")"; + EXPECT_THAT(EvaluateExpr(*LtExpr({num_val, nan_expr})), + Returns(testutil::Value(false))) + << "lt(" << ExprId(num_val) << ", NaN)"; + } + for (const auto& other_val : + ComparisonValueTestData::AllSupportedComparableValues()) { + bool is_numeric = false; + for (const auto& num_val : ComparisonValueTestData::NumericValues()) { + if (other_val == num_val) { + is_numeric = true; + break; + } + } + if (!is_numeric) { + EXPECT_THAT(EvaluateExpr(*LtExpr({nan_expr, other_val})), + Returns(testutil::Value(false))) + << "lt(NaN, " << ExprId(other_val) << ")"; + EXPECT_THAT(EvaluateExpr(*LtExpr({other_val, nan_expr})), + Returns(testutil::Value(false))) + << "lt(" << ExprId(other_val) << ", NaN)"; + } + } + EXPECT_THAT( + EvaluateExpr(*LtExpr({SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN()))), + SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN())))})), + Returns(testutil::Value(false))); +} + +TEST_F(LtFunctionTest, ErrorHandling) { + auto error_expr = std::make_shared("a.b"); + auto non_map_input = testutil::Doc("coll/doc", 1, testutil::Map("a", 123)); + + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT(EvaluateExpr(*LtExpr({error_expr, val}), non_map_input), + ReturnsUnset()); + EXPECT_THAT(EvaluateExpr(*LtExpr({val, error_expr}), non_map_input), + ReturnsUnset()); + } + EXPECT_THAT(EvaluateExpr(*LtExpr({error_expr, error_expr}), non_map_input), + ReturnsUnset()); + EXPECT_THAT( + EvaluateExpr(*LtExpr({error_expr, SharedConstant(model::NullValue())}), + non_map_input), + ReturnsUnset()); +} + +TEST_F(LtFunctionTest, MissingFieldReturnsUnset) { + EXPECT_THAT(EvaluateExpr(*LtExpr({std::make_shared("nonexistent"), + SharedConstant(testutil::Value(1LL))})), + ReturnsUnset()); + EXPECT_THAT( + EvaluateExpr(*LtExpr({SharedConstant(testutil::Value(1LL)), + std::make_shared("nonexistent")})), + ReturnsUnset()); +} + +// --- Lte (<=) Tests --- + +TEST_F(LteFunctionTest, EquivalentValuesReturnTrue) { + for (const auto& pair : ComparisonValueTestData::EquivalentValues()) { + EXPECT_THAT(EvaluateExpr(*LteExpr({pair.first, pair.second})), + Returns(testutil::Value(true))) + << "lte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(LteFunctionTest, LessThanValuesReturnTrue) { + for (const auto& pair : ComparisonValueTestData::LessThanValues()) { + EXPECT_THAT(EvaluateExpr(*LteExpr({pair.first, pair.second})), + Returns(testutil::Value(true))) + << "lte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(LteFunctionTest, GreaterThanValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::GreaterThanValues()) { + EXPECT_THAT(EvaluateExpr(*LteExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "lte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(LteFunctionTest, MixedTypeValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::MixedTypeValues()) { + EXPECT_THAT(EvaluateExpr(*LteExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "lte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +// --- Specific Lte Tests --- + +TEST_F(LteFunctionTest, NullOperandReturnsNull) { + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT( + EvaluateExpr(*LteExpr({SharedConstant(model::NullValue()), val})), + ReturnsNull()) + << "lte(null, " << ExprId(val) << ")"; + EXPECT_THAT( + EvaluateExpr(*LteExpr({val, SharedConstant(model::NullValue())})), + ReturnsNull()) + << "lte(" << ExprId(val) << ", null)"; + } + EXPECT_THAT(EvaluateExpr(*LteExpr({SharedConstant(model::NullValue()), + SharedConstant(model::NullValue())})), + ReturnsNull()); + EXPECT_THAT( + EvaluateExpr(*LteExpr({SharedConstant(model::NullValue()), + std::make_shared("nonexistent")})), + ReturnsUnset()); +} + +TEST_F(LteFunctionTest, NaNComparisonsReturnFalse) { + auto nan_expr = SharedConstant(std::numeric_limits::quiet_NaN()); + EXPECT_THAT(EvaluateExpr(*LteExpr({nan_expr, nan_expr})), + Returns(testutil::Value(false))); + + for (const auto& num_val : ComparisonValueTestData::NumericValues()) { + EXPECT_THAT(EvaluateExpr(*LteExpr({nan_expr, num_val})), + Returns(testutil::Value(false))) + << "lte(NaN, " << ExprId(num_val) << ")"; + EXPECT_THAT(EvaluateExpr(*LteExpr({num_val, nan_expr})), + Returns(testutil::Value(false))) + << "lte(" << ExprId(num_val) << ", NaN)"; + } + for (const auto& other_val : + ComparisonValueTestData::AllSupportedComparableValues()) { + bool is_numeric = false; + for (const auto& num_val : ComparisonValueTestData::NumericValues()) { + if (other_val == num_val) { + is_numeric = true; + break; + } + } + if (!is_numeric) { + EXPECT_THAT(EvaluateExpr(*LteExpr({nan_expr, other_val})), + Returns(testutil::Value(false))) + << "lte(NaN, " << ExprId(other_val) << ")"; + EXPECT_THAT(EvaluateExpr(*LteExpr({other_val, nan_expr})), + Returns(testutil::Value(false))) + << "lte(" << ExprId(other_val) << ", NaN)"; + } + } + EXPECT_THAT( + EvaluateExpr(*LteExpr({SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN()))), + SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN())))})), + Returns(testutil::Value(false))); +} + +TEST_F(LteFunctionTest, ErrorHandling) { + auto error_expr = std::make_shared("a.b"); + auto non_map_input = testutil::Doc("coll/doc", 1, testutil::Map("a", 123)); + + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT(EvaluateExpr(*LteExpr({error_expr, val}), non_map_input), + ReturnsUnset()); + EXPECT_THAT(EvaluateExpr(*LteExpr({val, error_expr}), non_map_input), + ReturnsUnset()); + } + EXPECT_THAT(EvaluateExpr(*LteExpr({error_expr, error_expr}), non_map_input), + ReturnsUnset()); + EXPECT_THAT( + EvaluateExpr(*LteExpr({error_expr, SharedConstant(model::NullValue())}), + non_map_input), + ReturnsUnset()); +} + +TEST_F(LteFunctionTest, MissingFieldReturnsUnset) { + EXPECT_THAT( + EvaluateExpr(*LteExpr({std::make_shared("nonexistent"), + SharedConstant(testutil::Value(1LL))})), + ReturnsUnset()); + EXPECT_THAT( + EvaluateExpr(*LteExpr({SharedConstant(testutil::Value(1LL)), + std::make_shared("nonexistent")})), + ReturnsUnset()); +} + +// --- Gt (>) Tests --- + +TEST_F(GtFunctionTest, EquivalentValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::EquivalentValues()) { + EXPECT_THAT(EvaluateExpr(*GtExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "gt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(GtFunctionTest, LessThanValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::LessThanValues()) { + EXPECT_THAT(EvaluateExpr(*GtExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "gt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(GtFunctionTest, GreaterThanValuesReturnTrue) { + for (const auto& pair : ComparisonValueTestData::GreaterThanValues()) { + // This set includes pairs like {1.0, 1} which compare as !GreaterThan. + // We expect false for those, true otherwise. + auto left_const = + std::dynamic_pointer_cast(pair.first); + auto right_const = + std::dynamic_pointer_cast(pair.second); + EXPECT_THAT(EvaluateExpr(*GtExpr({pair.first, pair.second})), + Returns(testutil::Value(true))) + << "gt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(GtFunctionTest, MixedTypeValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::MixedTypeValues()) { + EXPECT_THAT(EvaluateExpr(*GtExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "gt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +// --- Specific Gt Tests --- + +TEST_F(GtFunctionTest, NullOperandReturnsNull) { + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT( + EvaluateExpr(*GtExpr({SharedConstant(model::NullValue()), val})), + ReturnsNull()) + << "gt(null, " << ExprId(val) << ")"; + EXPECT_THAT( + EvaluateExpr(*GtExpr({val, SharedConstant(model::NullValue())})), + ReturnsNull()) + << "gt(" << ExprId(val) << ", null)"; + } + EXPECT_THAT(EvaluateExpr(*GtExpr({SharedConstant(model::NullValue()), + SharedConstant(model::NullValue())})), + ReturnsNull()); + EXPECT_THAT( + EvaluateExpr(*GtExpr({SharedConstant(model::NullValue()), + std::make_shared("nonexistent")})), + ReturnsUnset()); +} + +TEST_F(GtFunctionTest, NaNComparisonsReturnFalse) { + auto nan_expr = SharedConstant(std::numeric_limits::quiet_NaN()); + EXPECT_THAT(EvaluateExpr(*GtExpr({nan_expr, nan_expr})), + Returns(testutil::Value(false))); + + for (const auto& num_val : ComparisonValueTestData::NumericValues()) { + EXPECT_THAT(EvaluateExpr(*GtExpr({nan_expr, num_val})), + Returns(testutil::Value(false))) + << "gt(NaN, " << ExprId(num_val) << ")"; + EXPECT_THAT(EvaluateExpr(*GtExpr({num_val, nan_expr})), + Returns(testutil::Value(false))) + << "gt(" << ExprId(num_val) << ", NaN)"; + } + for (const auto& other_val : + ComparisonValueTestData::AllSupportedComparableValues()) { + bool is_numeric = false; + for (const auto& num_val : ComparisonValueTestData::NumericValues()) { + if (other_val == num_val) { + is_numeric = true; + break; + } + } + if (!is_numeric) { + EXPECT_THAT(EvaluateExpr(*GtExpr({nan_expr, other_val})), + Returns(testutil::Value(false))) + << "gt(NaN, " << ExprId(other_val) << ")"; + EXPECT_THAT(EvaluateExpr(*GtExpr({other_val, nan_expr})), + Returns(testutil::Value(false))) + << "gt(" << ExprId(other_val) << ", NaN)"; + } + } + EXPECT_THAT( + EvaluateExpr(*GtExpr({SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN()))), + SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN())))})), + Returns(testutil::Value(false))); +} + +TEST_F(GtFunctionTest, ErrorHandling) { + auto error_expr = std::make_shared("a.b"); + auto non_map_input = testutil::Doc("coll/doc", 1, testutil::Map("a", 123)); + + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT(EvaluateExpr(*GtExpr({error_expr, val}), non_map_input), + ReturnsUnset()); + EXPECT_THAT(EvaluateExpr(*GtExpr({val, error_expr}), non_map_input), + ReturnsUnset()); + } + EXPECT_THAT(EvaluateExpr(*GtExpr({error_expr, error_expr}), non_map_input), + ReturnsUnset()); + EXPECT_THAT( + EvaluateExpr(*GtExpr({error_expr, SharedConstant(model::NullValue())}), + non_map_input), + ReturnsUnset()); +} + +TEST_F(GtFunctionTest, MissingFieldReturnsUnset) { + EXPECT_THAT(EvaluateExpr(*GtExpr({std::make_shared("nonexistent"), + SharedConstant(testutil::Value(1LL))})), + ReturnsUnset()); + EXPECT_THAT( + EvaluateExpr(*GtExpr({SharedConstant(testutil::Value(1LL)), + std::make_shared("nonexistent")})), + ReturnsUnset()); +} + +// --- Gte (>=) Tests --- + +TEST_F(GteFunctionTest, EquivalentValuesReturnTrue) { + for (const auto& pair : ComparisonValueTestData::EquivalentValues()) { + EXPECT_THAT(EvaluateExpr(*GteExpr({pair.first, pair.second})), + Returns(testutil::Value(true))) + << "gte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(GteFunctionTest, LessThanValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::LessThanValues()) { + EXPECT_THAT(EvaluateExpr(*GteExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "gte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(GteFunctionTest, GreaterThanValuesReturnTrue) { + for (const auto& pair : ComparisonValueTestData::GreaterThanValues()) { + EXPECT_THAT(EvaluateExpr(*GteExpr({pair.first, pair.second})), + Returns(testutil::Value(true))) + << "gte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +TEST_F(GteFunctionTest, MixedTypeValuesReturnFalse) { + for (const auto& pair : ComparisonValueTestData::MixedTypeValues()) { + EXPECT_THAT(EvaluateExpr(*GteExpr({pair.first, pair.second})), + Returns(testutil::Value(false))) + << "gte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")"; + } +} + +// --- Specific Gte Tests --- + +TEST_F(GteFunctionTest, NullOperandReturnsNull) { + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT( + EvaluateExpr(*GteExpr({SharedConstant(model::NullValue()), val})), + ReturnsNull()) + << "gte(null, " << ExprId(val) << ")"; + EXPECT_THAT( + EvaluateExpr(*GteExpr({val, SharedConstant(model::NullValue())})), + ReturnsNull()) + << "gte(" << ExprId(val) << ", null)"; + } + EXPECT_THAT(EvaluateExpr(*GteExpr({SharedConstant(model::NullValue()), + SharedConstant(model::NullValue())})), + ReturnsNull()); + EXPECT_THAT( + EvaluateExpr(*GteExpr({SharedConstant(model::NullValue()), + std::make_shared("nonexistent")})), + ReturnsUnset()); +} + +TEST_F(GteFunctionTest, NaNComparisonsReturnFalse) { + auto nan_expr = SharedConstant(std::numeric_limits::quiet_NaN()); + EXPECT_THAT(EvaluateExpr(*GteExpr({nan_expr, nan_expr})), + Returns(testutil::Value(false))); + + for (const auto& num_val : ComparisonValueTestData::NumericValues()) { + EXPECT_THAT(EvaluateExpr(*GteExpr({nan_expr, num_val})), + Returns(testutil::Value(false))) + << "gte(NaN, " << ExprId(num_val) << ")"; + EXPECT_THAT(EvaluateExpr(*GteExpr({num_val, nan_expr})), + Returns(testutil::Value(false))) + << "gte(" << ExprId(num_val) << ", NaN)"; + } + for (const auto& other_val : + ComparisonValueTestData::AllSupportedComparableValues()) { + bool is_numeric = false; + for (const auto& num_val : ComparisonValueTestData::NumericValues()) { + if (other_val == num_val) { + is_numeric = true; + break; + } + } + if (!is_numeric) { + EXPECT_THAT(EvaluateExpr(*GteExpr({nan_expr, other_val})), + Returns(testutil::Value(false))) + << "gte(NaN, " << ExprId(other_val) << ")"; + EXPECT_THAT(EvaluateExpr(*GteExpr({other_val, nan_expr})), + Returns(testutil::Value(false))) + << "gte(" << ExprId(other_val) << ", NaN)"; + } + } + EXPECT_THAT( + EvaluateExpr(*GteExpr({SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN()))), + SharedConstant(testutil::Array(testutil::Value( + std::numeric_limits::quiet_NaN())))})), + Returns(testutil::Value(false))); +} + +TEST_F(GteFunctionTest, ErrorHandling) { + auto error_expr = std::make_shared("a.b"); + auto non_map_input = testutil::Doc("coll/doc", 1, testutil::Map("a", 123)); + + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT(EvaluateExpr(*GteExpr({error_expr, val}), non_map_input), + ReturnsUnset()); + EXPECT_THAT(EvaluateExpr(*GteExpr({val, error_expr}), non_map_input), + ReturnsUnset()); + } + EXPECT_THAT(EvaluateExpr(*GteExpr({error_expr, error_expr}), non_map_input), + ReturnsUnset()); + EXPECT_THAT( + EvaluateExpr(*GteExpr({error_expr, SharedConstant(model::NullValue())}), + non_map_input), + ReturnsUnset()); +} - ASSERT_TRUE(model::Equals(*result.value(), model::TrueValue())); +TEST_F(GteFunctionTest, MissingFieldReturnsUnset) { + EXPECT_THAT( + EvaluateExpr(*GteExpr({std::make_shared("nonexistent"), + SharedConstant(testutil::Value(1LL))})), + ReturnsUnset()); + EXPECT_THAT( + EvaluateExpr(*GteExpr({SharedConstant(testutil::Value(1LL)), + std::make_shared("nonexistent")})), + ReturnsUnset()); } -} // namespace core -} // namespace firestore -} // namespace firebase +} // namespace core +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/testutil/expression_test_util.cc b/Firestore/core/test/unit/testutil/expression_test_util.cc new file mode 100644 index 00000000000..cceeeae833a --- /dev/null +++ b/Firestore/core/test/unit/testutil/expression_test_util.cc @@ -0,0 +1,131 @@ +/* + * Copyright 2025 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/test/unit/testutil/expression_test_util.h" + +#include // For std::numeric_limits +#include // Required for numeric_limits +#include // For std::shared_ptr +#include + +#include "Firestore/core/include/firebase/firestore/geo_point.h" +#include "Firestore/core/include/firebase/firestore/timestamp.h" +#include "Firestore/core/src/model/value_util.h" // For Value, Array, Map, BlobValue, RefValue + +namespace firebase { +namespace firestore { +namespace testutil { + +// Assuming Java long maps to int64_t in C++ +const int64_t kMaxLongExactlyRepresentableAsDouble = 1LL + << 53; // 9007199254740992 + +// --- Initialize Static Data Members --- + +const std::vector> + ComparisonValueTestData::BOOLEAN_VALUES = {SharedConstant(false), + SharedConstant(true)}; + +const std::vector> + ComparisonValueTestData::NUMERIC_VALUES = { + SharedConstant(-std::numeric_limits::infinity()), + SharedConstant(-std::numeric_limits::max()), + SharedConstant(std::numeric_limits::min()), + SharedConstant(-kMaxLongExactlyRepresentableAsDouble), + SharedConstant(-1LL), + SharedConstant(-0.5), + SharedConstant(-std::numeric_limits::min()), // -MIN_NORMAL + SharedConstant( + -std::numeric_limits::denorm_min()), // -MIN_VALUE + // (denormalized) + SharedConstant( + 0.0), // Include 0.0 (represents both 0.0 and -0.0 for ordering) + SharedConstant( + std::numeric_limits::denorm_min()), // MIN_VALUE + // (denormalized) + SharedConstant(std::numeric_limits::min()), // MIN_NORMAL + SharedConstant(0.5), + SharedConstant(1LL), + SharedConstant(42LL), + SharedConstant(kMaxLongExactlyRepresentableAsDouble), + SharedConstant(std::numeric_limits::max()), + SharedConstant(std::numeric_limits::max()), + SharedConstant(std::numeric_limits::infinity()), +}; + +const std::vector> + ComparisonValueTestData::TIMESTAMP_VALUES = { + SharedConstant(Timestamp(-42, 0)), + SharedConstant(Timestamp(-42, 42000000)), // 42 ms = 42,000,000 ns + SharedConstant(Timestamp(0, 0)), + SharedConstant(Timestamp(0, 42000000)), + SharedConstant(Timestamp(42, 0)), + SharedConstant(Timestamp(42, 42000000))}; + +const std::vector> + ComparisonValueTestData::STRING_VALUES = { + SharedConstant(""), SharedConstant("abcdefgh"), + // SharedConstant("fouxdufafa".repeat(200)), // String repeat not std + // C++ + SharedConstant("santé"), SharedConstant("santé et bonheur")}; + +const std::vector> ComparisonValueTestData::BYTE_VALUES = + { + SharedConstant(*BlobValue()), // Empty - use default constructor + SharedConstant(*BlobValue(0, 2, 56, 42)), // Use variadic args + SharedConstant(*BlobValue(2, 26)), // Use variadic args + SharedConstant(*BlobValue(2, 26, 31)), // Use variadic args + // SharedConstant(*BlobValue(std::vector(...))), // Large blob +}; + +const std::vector> + ComparisonValueTestData::ENTITY_REF_VALUES = { + RefConstant("foo/bar"), RefConstant("foo/bar/qux/a"), + RefConstant("foo/bar/qux/bleh"), RefConstant("foo/bar/qux/hi"), + RefConstant("foo/bar/tonk/a"), RefConstant("foo/baz")}; + +const std::vector> ComparisonValueTestData::GEO_VALUES = { + SharedConstant(GeoPoint(-87.0, -92.0)), + SharedConstant(GeoPoint(-87.0, 0.0)), + SharedConstant(GeoPoint(-87.0, 42.0)), + SharedConstant(GeoPoint(0.0, -92.0)), + SharedConstant(GeoPoint(0.0, 0.0)), + SharedConstant(GeoPoint(0.0, 42.0)), + SharedConstant(GeoPoint(42.0, -92.0)), + SharedConstant(GeoPoint(42.0, 0.0)), + SharedConstant(GeoPoint(42.0, 42.0))}; + +const std::vector> ComparisonValueTestData::ARRAY_VALUES = + {SharedConstant(Array()), + SharedConstant(Array(true, 15LL)), + SharedConstant(Array(1LL, 2LL)), + SharedConstant(Array(Value(Timestamp(12, 0)))), + SharedConstant(Array("foo")), + SharedConstant(Array("foo", "bar")), + SharedConstant(Array(Value(GeoPoint(0, 0)))), + SharedConstant(Array(Map()))}; + +const std::vector> ComparisonValueTestData::MAP_VALUES = { + SharedConstant(Map()), + SharedConstant(Map("ABA", "qux")), + SharedConstant(Map("aba", "hello")), + SharedConstant(Map("aba", "hello", "foo", true)), + SharedConstant(Map("aba", "qux")), + SharedConstant(Map("foo", "aaa"))}; + +} // namespace testutil +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/testutil/expression_test_util.h b/Firestore/core/test/unit/testutil/expression_test_util.h new file mode 100644 index 00000000000..870b67f22e2 --- /dev/null +++ b/Firestore/core/test/unit/testutil/expression_test_util.h @@ -0,0 +1,470 @@ +/* + * Copyright 2025 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_TEST_UNIT_TESTUTIL_EXPRESSION_TEST_UTIL_H_ +#define FIRESTORE_CORE_TEST_UNIT_TESTUTIL_EXPRESSION_TEST_UTIL_H_ + +#include // For std::sort +#include // For std::initializer_list +#include // For std::numeric_limits +#include // For std::shared_ptr, std::make_shared +#include // For std::ostream +#include // For std::string +#include // For std::move, std::pair +#include + +#include "Firestore/core/include/firebase/firestore/geo_point.h" +#include "Firestore/core/include/firebase/firestore/timestamp.h" +#include "Firestore/core/src/api/expressions.h" +#include "Firestore/core/src/api/stages.h" +#include "Firestore/core/src/core/expressions_eval.h" +#include "Firestore/core/src/model/database_id.h" +#include "Firestore/core/src/model/document_key.h" +#include "Firestore/core/src/model/mutable_document.h" +#include "Firestore/core/src/model/object_value.h" +#include "Firestore/core/src/model/snapshot_version.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/hard_assert.h" +#include "Firestore/core/src/util/string_format.h" // For StringFormat +#include "Firestore/core/test/unit/testutil/testutil.h" + +#include "absl/strings/escaping.h" // For absl::HexStringToBytes +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace testutil { + +using api::Constant; +using api::EvaluateContext; +using api::Expr; +using api::FunctionExpr; +using core::EvaluableExpr; +using core::EvaluateResult; +using model::DatabaseId; +using model::DocumentKey; +using model::GetTypeOrder; +using model::MutableDocument; // PipelineInputOutput is MutableDocument +using model::ObjectValue; +using model::SnapshotVersion; +using nanopb::Message; +using remote::Serializer; +using util::StringFormat; + +// --- Constant Expression Helpers --- + +inline std::shared_ptr SharedConstant(int64_t value) { + return std::make_shared(Value(value)); +} + +inline std::shared_ptr SharedConstant(double value) { + return std::make_shared(Value(value)); +} + +inline std::shared_ptr SharedConstant(const char* value) { + return std::make_shared(Value(value)); +} + +inline std::shared_ptr SharedConstant(bool value) { + return std::make_shared(Value(value)); +} + +inline std::shared_ptr SharedConstant(Timestamp value) { + return std::make_shared(Value(value)); +} + +inline std::shared_ptr SharedConstant(GeoPoint value) { + return std::make_shared(Value(value)); +} + +// Overload for google_firestore_v1_Value +inline std::shared_ptr SharedConstant( + const google_firestore_v1_Value& value) { + // Constant expects a Message, so clone it. + return std::make_shared(model::DeepClone(value)); +} + +inline std::shared_ptr SharedConstant( + Message value) { + // Constant expects a Message, so clone it. + return std::make_shared(Value(std::move(value))); +} + +inline std::shared_ptr SharedConstant( + Message value) { + // Constant expects a Message, so clone it. + return std::make_shared(std::move(value)); +} + +// Helper to create a Reference Value Constant for tests +// Needs to be defined before use in ENTITY_REF_VALUES if defined statically +inline std::shared_ptr RefConstant(const std::string& path) { + static const DatabaseId db_id("test-project", "test-database"); + // model::RefValue returns a Message, pass its content to + // SharedConstant + return SharedConstant( + *model::RefValue(db_id, DocumentKey::FromPathString(path))); +} + +inline std::shared_ptr AddExpr( + std::initializer_list> params) { + return std::make_shared( + "add", std::vector>(params)); +} + +inline std::shared_ptr SubtractExpr( + std::initializer_list> params) { + return std::make_shared( + "subtract", std::vector>(params)); +} + +inline std::shared_ptr MultiplyExpr( + std::initializer_list> params) { + return std::make_shared( + "multiply", std::vector>(params)); +} + +inline std::shared_ptr DivideExpr( + std::initializer_list> params) { + return std::make_shared( + "divide", std::vector>(params)); +} + +inline std::shared_ptr ModExpr( + std::initializer_list> params) { + return std::make_shared( + "mod", std::vector>(params)); +} + +// --- Comparison Expression Helpers --- + +inline std::shared_ptr EqExpr( + std::initializer_list> params) { + HARD_ASSERT(params.size() == 2, "EqExpr requires exactly 2 parameters"); + return std::make_shared( + "eq", std::vector>(params)); +} + +inline std::shared_ptr NeqExpr( + std::initializer_list> params) { + HARD_ASSERT(params.size() == 2, "NeqExpr requires exactly 2 parameters"); + return std::make_shared( + "neq", std::vector>(params)); +} + +inline std::shared_ptr LtExpr( + std::initializer_list> params) { + HARD_ASSERT(params.size() == 2, "LtExpr requires exactly 2 parameters"); + return std::make_shared( + "lt", std::vector>(params)); +} + +inline std::shared_ptr LteExpr( + std::initializer_list> params) { + HARD_ASSERT(params.size() == 2, "LteExpr requires exactly 2 parameters"); + return std::make_shared( + "lte", std::vector>(params)); +} + +inline std::shared_ptr GtExpr( + std::initializer_list> params) { + HARD_ASSERT(params.size() == 2, "GtExpr requires exactly 2 parameters"); + return std::make_shared( + "gt", std::vector>(params)); +} + +inline std::shared_ptr GteExpr( + std::initializer_list> params) { + HARD_ASSERT(params.size() == 2, "GteExpr requires exactly 2 parameters"); + return std::make_shared( + "gte", std::vector>(params)); +} + +// --- Comparison Test Data --- + +// Defines pairs of expressions for comparison testing. +using ExprPair = std::pair, std::shared_ptr>; + +namespace { +// Helper to check if two expressions (assumed Constants) have comparable types. +// Assuming Constant::value() returns the nanopb::Message object. +bool IsTypeComparable(const std::shared_ptr& left, + const std::shared_ptr& right) { + auto left_const = std::dynamic_pointer_cast(left); + auto right_const = std::dynamic_pointer_cast(right); + HARD_ASSERT(left_const && right_const, + "IsTypeComparable expects Constant expressions"); + // Access the underlying nanopb message via *value() + return GetTypeOrder(left_const->to_proto()) == + GetTypeOrder(right_const->to_proto()); +} +} // namespace + +struct ComparisonValueTestData { + private: + // Define the base value lists matching TypeScript (assumed sorted internally) + static const std::vector> BOOLEAN_VALUES; + static const std::vector> NUMERIC_VALUES; + static const std::vector> TIMESTAMP_VALUES; + static const std::vector> STRING_VALUES; + static const std::vector> BYTE_VALUES; + static const std::vector> ENTITY_REF_VALUES; + static const std::vector> GEO_VALUES; + static const std::vector> ARRAY_VALUES; + static const std::vector> MAP_VALUES; + // Note: VECTOR_VALUES omitted as VectorValue is not yet supported in C++ + // expressions + + public: + // A representative list of all comparable value types for null/error tests. + // Excludes NullValue itself. Concatenated in TypeOrder. + static const std::vector>& + AllSupportedComparableValues() { + static const std::vector> combined = [] { + std::vector> all_values; + // Concatenate in Firestore TypeOrder + all_values.insert(all_values.end(), BOOLEAN_VALUES.begin(), + BOOLEAN_VALUES.end()); + all_values.insert(all_values.end(), NUMERIC_VALUES.begin(), + NUMERIC_VALUES.end()); + all_values.insert(all_values.end(), TIMESTAMP_VALUES.begin(), + TIMESTAMP_VALUES.end()); + all_values.insert(all_values.end(), STRING_VALUES.begin(), + STRING_VALUES.end()); + all_values.insert(all_values.end(), BYTE_VALUES.begin(), + BYTE_VALUES.end()); + all_values.insert(all_values.end(), ENTITY_REF_VALUES.begin(), + ENTITY_REF_VALUES.end()); + all_values.insert(all_values.end(), GEO_VALUES.begin(), GEO_VALUES.end()); + all_values.insert(all_values.end(), ARRAY_VALUES.begin(), + ARRAY_VALUES.end()); + all_values.insert(all_values.end(), MAP_VALUES.begin(), MAP_VALUES.end()); + // No sort needed if base lists are sorted and concatenated correctly. + return all_values; + }(); + return combined; + } + + // Values that should compare as equal. + static std::vector EquivalentValues() { + std::vector results; + const auto& all_values = AllSupportedComparableValues(); + for (const auto& value : all_values) { + results.push_back({value, value}); + } + + results.push_back({SharedConstant(-42LL), SharedConstant(-42.0)}); + results.push_back({SharedConstant(-42.0), SharedConstant(-42LL)}); + results.push_back({SharedConstant(42LL), SharedConstant(42.0)}); + results.push_back({SharedConstant(42.0), SharedConstant(42LL)}); + + results.push_back({SharedConstant(0.0), SharedConstant(-0.0)}); + results.push_back({SharedConstant(-0.0), SharedConstant(0.0)}); + + results.push_back({SharedConstant(0LL), SharedConstant(-0.0)}); + results.push_back({SharedConstant(-0.0), SharedConstant(0LL)}); + + results.push_back({SharedConstant(0LL), SharedConstant(0.0)}); + results.push_back({SharedConstant(0.0), SharedConstant(0LL)}); + + return results; + } + + // Values where left < right. Relies on AllSupportedComparableValues being + // sorted. + static std::vector LessThanValues() { + std::vector results; + const auto& all_values = AllSupportedComparableValues(); + for (size_t i = 0; i < all_values.size(); ++i) { + for (size_t j = i + 1; j < all_values.size(); ++j) { + const auto& left = all_values[i]; + const auto& right = all_values[j]; + if (IsTypeComparable(left, right)) { + // Since all_values is sorted by type then value, + // and i < j, if types are comparable, left < right. + // This includes pairs like {1, 1.0} which compare as !lessThan. + // The calling test needs to handle the expected result. + results.push_back({left, right}); + } + } + } + return results; + } + + // Values where left > right. Relies on AllSupportedComparableValues being + // sorted. + static std::vector GreaterThanValues() { + std::vector results; + const auto& all_values = AllSupportedComparableValues(); + for (size_t i = 0; i < all_values.size(); ++i) { + for (size_t j = i + 1; j < all_values.size(); ++j) { + const auto& left = all_values[i]; // left is smaller + const auto& right = all_values[j]; // right is larger + if (IsTypeComparable(left, right)) { + // Since all_values is sorted, if types match, right > left. + // Add the reversed pair {right, left}. + // This includes pairs like {1.0, 1} which compare as !greaterThan. + // The calling test needs to handle the expected result. + results.push_back({right, left}); // Add reversed pair + } + } + } + return results; + } + + // Values of different types. + static std::vector MixedTypeValues() { + std::vector results; + const auto& all_values = AllSupportedComparableValues(); + for (size_t i = 0; i < all_values.size(); ++i) { + for (size_t j = 0; j < all_values.size(); ++j) { // Note: j starts from 0 + const auto& left = all_values[i]; + const auto& right = all_values[j]; + if (!IsTypeComparable(left, right)) { + results.push_back({left, right}); + } + } + } + return results; + } + + // Numeric values for NaN tests (subset of NUMERIC_VALUES) + static const std::vector>& NumericValues() { + return NUMERIC_VALUES; + } +}; + +static remote::Serializer serializer(model::DatabaseId("test-project")); + +// Creates a default evaluation context. +inline api::EvaluateContext NewContext() { + return {&serializer}; +} + +// Helper function to evaluate an expression and return the result. +// Creates a dummy context and input document. +inline EvaluateResult EvaluateExpr(const Expr& expr) { + // Use a dummy input document (FoundDocument with empty data) + model::PipelineInputOutput input = testutil::Doc("coll/doc", 1); + + std::unique_ptr evaluable = expr.ToEvaluable(); + HARD_ASSERT(evaluable != nullptr, "Failed to create evaluable expression"); + return evaluable->Evaluate(NewContext(), input); +} + +// Helper function to evaluate an expression with a specific input. +inline EvaluateResult EvaluateExpr(const Expr& expr, + const model::PipelineInputOutput& input) { + std::unique_ptr evaluable = expr.ToEvaluable(); + HARD_ASSERT(evaluable != nullptr, "Failed to create evaluable expression"); + return evaluable->Evaluate(NewContext(), input); +} + +// --- Custom Gmock Matchers --- + +MATCHER(ReturnsError, std::string("evaluates to error ")) { + // 'arg' is the value being tested + if (arg.type() == EvaluateResult::ResultType::kError) { + return true; + } else { + *result_listener << "the result type is " + << testing::PrintToString(arg.type()); + return false; + } +} + +MATCHER(ReturnsNull, std::string("evaluates to null ")) { + // 'arg' is the value being tested + if (arg.type() == EvaluateResult::ResultType::kNull) { + return true; + } else { + *result_listener << "the result type is " + << testing::PrintToString(arg.type()); + return false; + } +} + +MATCHER(ReturnsUnset, std::string("evaluates to unset ")) { + // 'arg' is the value being tested + if (arg.type() == EvaluateResult::ResultType::kUnset) { + return true; + } else { + *result_listener << "the result type is " + << testing::PrintToString(arg.type()); + return false; + } +} + +template +class ReturnsMatcherImpl : public testing::MatcherInterface { + public: + explicit ReturnsMatcherImpl( + Message&& expected_value) + : expected_value_(std::move(expected_value)) { + } + + bool MatchAndExplain(T arg, + testing::MatchResultListener* listener) const override { + if (!arg.IsErrorOrUnset()) { + // Value is valid, proceed with comparison + if (model::IsNaNValue(*expected_value_)) { + *listener << "expected NaN, but got " + << model::CanonicalId(*arg.value()); + // Special handling for NaN: Both must be NaN to match + return model::IsNaNValue(*arg.value()); + } else { + *listener << "expected value " << model::CanonicalId(*expected_value_) + << ", but got " << model::CanonicalId(*arg.value()); + // Standard equality comparison + return model::Equals(*arg.value(), *expected_value_); + } + } else { + // The actual result 'arg' is an error or unset, but we expected a value. + // This is considered a mismatch. + *listener << "expected value, but got result type" + << testing::PrintToString(arg.type()); + return false; + } + } + + void DescribeTo(std::ostream* os) const override { + *os << "evaluates to value " << testing::PrintToString(expected_value_); + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not evaluate to value " + << testing::PrintToString(expected_value_); + } + + private: + Message expected_value_; +}; + +template +inline testing::Matcher Returns( + Message&& expected_value) { + return testing::MakeMatcher( + new ReturnsMatcherImpl(std::move(expected_value))); +} + +} // namespace testutil +} // namespace firestore +} // namespace firebase + +#endif // FIRESTORE_CORE_TEST_UNIT_TESTUTIL_EXPRESSION_TEST_UTIL_H_ diff --git a/cmake/external/leveldb_patch.py b/cmake/external/leveldb_patch.py old mode 100644 new mode 100755