From 60add0f0668a100213dd3c65eb4f1a62a88129d8 Mon Sep 17 00:00:00 2001 From: wu-hui Date: Wed, 16 Apr 2025 14:18:24 -0400 Subject: [PATCH] [realppl 4] Array, debug, field and logical expressions --- .../Firestore.xcodeproj/project.pbxproj | 62 +- Firestore/core/src/core/expressions_eval.cc | 778 ++++++++++- Firestore/core/src/core/expressions_eval.h | 266 ++++ Firestore/core/src/model/value_util.cc | 14 + Firestore/core/src/model/value_util.h | 8 + .../test/unit/core/expressions/array_test.cc | 375 ++++++ .../test/unit/core/expressions/debug_test.cc | 150 +++ .../test/unit/core/expressions/field_test.cc | 57 + .../unit/core/expressions/logical_test.cc | 1155 +++++++++++++++++ .../test/unit/testutil/expression_test_util.h | 125 ++ 10 files changed, 2982 insertions(+), 8 deletions(-) create mode 100644 Firestore/core/test/unit/core/expressions/array_test.cc create mode 100644 Firestore/core/test/unit/core/expressions/debug_test.cc create mode 100644 Firestore/core/test/unit/core/expressions/field_test.cc create mode 100644 Firestore/core/test/unit/core/expressions/logical_test.cc diff --git a/Firestore/Example/Firestore.xcodeproj/project.pbxproj b/Firestore/Example/Firestore.xcodeproj/project.pbxproj index f2b190cc75b..bd8caca6180 100644 --- a/Firestore/Example/Firestore.xcodeproj/project.pbxproj +++ b/Firestore/Example/Firestore.xcodeproj/project.pbxproj @@ -160,6 +160,7 @@ 15BF63DFF3A7E9A5376C4233 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */; }; 15F54E9538839D56A40C5565 /* watch_change_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2D7472BC70C024D736FF74D9 /* watch_change_test.cc */; }; 160B8B6F32963E94CB70B14F /* leveldb_query_engine_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = DB1F1E1B1ED15E8D042144B1 /* leveldb_query_engine_test.cc */; }; + 1618D290DC26C76A1F0C87D7 /* field_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 24F0F49F016E65823E0075DB /* field_test.cc */; }; 162291531D29B002F6872A7F /* Validation_BloomFilterTest_MD5_500_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = D22D4C211AC32E4F8B4883DA /* Validation_BloomFilterTest_MD5_500_0001_bloom_filter_proto.json */; }; 163C0D0E65EB658E3B6070BC /* settings_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = DD12BC1DB2480886D2FB0005 /* settings_test.cc */; }; 167659CDCA47B450F2441454 /* index_backfiller_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1F50E872B3F117A674DA8E94 /* index_backfiller_test.cc */; }; @@ -202,6 +203,7 @@ 1BB0C34B2E8D8BCC5882430A /* garbage_collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = AAED89D7690E194EF3BA1132 /* garbage_collection_spec_test.json */; }; 1BD772FABD69673BF5864110 /* Validation_BloomFilterTest_MD5_5000_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = B0520A41251254B3C24024A3 /* Validation_BloomFilterTest_MD5_5000_01_membership_test_result.json */; }; 1BF1F9A0CBB6B01654D3C2BE /* field_transform_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7515B47C92ABEEC66864B55C /* field_transform_test.cc */; }; + 1C12B0A8896ACAD736B5CDC7 /* field_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 24F0F49F016E65823E0075DB /* field_test.cc */; }; 1C19D796DB6715368407387A /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; }; 1C4F88DDEFA6FA23E9E4DB4B /* mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3068AA9DFBBA86C1FE2A946E /* mutation_queue_test.cc */; }; 1C7254742A9F6F7042C9D78E /* FSTEventAccumulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */; }; @@ -260,8 +262,10 @@ 248DE4F56DD938F4DBCCF39B /* bundle_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6ECAF7DE28A19C69DF386D88 /* bundle_reader_test.cc */; }; 24B75C63BDCD5551B2F69901 /* testing_hooks_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A002425BC4FC4E805F4175B6 /* testing_hooks_test.cc */; }; 24CB39421C63CD87242B31DF /* bundle_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6ECAF7DE28A19C69DF386D88 /* bundle_reader_test.cc */; }; + 25202D64249BFE38AB8B8DA9 /* logical_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51619F8CFF13B0CDD13EDC3 /* logical_test.cc */; }; 254CD651CB621D471BC5AC12 /* target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B5C37696557C81A6C2B7271A /* target_cache_test.cc */; }; 258B372CF33B7E7984BBA659 /* fake_target_metadata_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = 71140E5D09C6E76F7C71B2FC /* fake_target_metadata_provider.cc */; }; + 25937E75A75B77DDA4D2FCF5 /* debug_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F6DBD8EDF0074DD0079ECCE6 /* debug_test.cc */; }; 25A75DFA730BAD21A5538EC5 /* document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D821C2DDC800EFB9CC /* document.pb.cc */; }; 25C167BAA4284FC951206E1F /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; 25D74F38A5EE96CC653ABB49 /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; @@ -375,6 +379,7 @@ 36FD4CE79613D18BC783C55B /* string_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0EE5300F8233D14025EF0456 /* string_apple_test.mm */; }; 37286D731E432CB873354357 /* remote_event_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 584AE2C37A55B408541A6FF3 /* remote_event_test.cc */; }; 37461AF1ACC2E64DF1709736 /* Validation_BloomFilterTest_MD5_1_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 0D964D4936953635AC7E0834 /* Validation_BloomFilterTest_MD5_1_01_bloom_filter_proto.json */; }; + 37664236439C338A73A984B9 /* debug_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F6DBD8EDF0074DD0079ECCE6 /* debug_test.cc */; }; 3783E25DFF9E5C0896D34FEF /* index_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 8C7278B604B8799F074F4E8C /* index_spec_test.json */; }; 37C4BF11C8B2B8B54B5ED138 /* string_apple_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */; }; 37EC6C6EA9169BB99078CA96 /* reference_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 132E32997D781B896672D30A /* reference_set_test.cc */; }; @@ -397,6 +402,7 @@ 3AC147E153D4A535B71C519E /* sorted_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4C20A36DBB00BCEB75 /* sorted_set_test.cc */; }; 3AFBEF94A35034719477C066 /* random_access_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 014C60628830D95031574D15 /* random_access_queue_test.cc */; }; 3B1E27D951407FD237E64D07 /* FirestoreEncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1235769422B86E65007DDFA9 /* FirestoreEncoderTests.swift */; }; + 3B229A902E93497D4B559F80 /* array_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0458BABD8F8738AD16F4A2FE /* array_test.cc */; }; 3B23E21D5D7ACF54EBD8CF67 /* memory_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9765D47FA12FA283F4EFAD02 /* memory_lru_garbage_collector_test.cc */; }; 3B256CCF6AEEE12E22F16BB8 /* hashing_test_apple.mm in Sources */ = {isa = PBXBuildFile; fileRef = B69CF3F02227386500B281C8 /* hashing_test_apple.mm */; }; 3B37BD3C13A66625EC82CF77 /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; }; @@ -445,6 +451,7 @@ 44A8B51C05538A8DACB85578 /* byte_stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 432C71959255C5DBDF522F52 /* byte_stream_test.cc */; }; 44C4244E42FFFB6E9D7F28BA /* byte_stream_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 432C71959255C5DBDF522F52 /* byte_stream_test.cc */; }; 44EAF3E6EAC0CC4EB2147D16 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */; }; + 45070DD0F8428BB68E6895C6 /* logical_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51619F8CFF13B0CDD13EDC3 /* logical_test.cc */; }; 451EFFB413364E5A420F8B2D /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; 4562CDD90F5FF0491F07C5DA /* leveldb_opener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */; }; 457171CE2510EEA46F7D8A30 /* FIRFirestoreTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FAFF203E56F8009C9584 /* FIRFirestoreTests.mm */; }; @@ -463,6 +470,7 @@ 474DF520B9859479845C8A4D /* bundle_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */; }; 475FE2D34C6555A54D77A054 /* empty_credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8FA60B08D59FEA0D6751E87F /* empty_credentials_provider_test.cc */; }; 476AE05E0878007DE1BF5460 /* comparison_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 87DD1A65EBA9FFC1FFAAE657 /* comparison_test.cc */; }; + 477D5B6AB66340FEA10B6D23 /* logical_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51619F8CFF13B0CDD13EDC3 /* logical_test.cc */; }; 4781186C01D33E67E07F0D0D /* orderby_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A21F315EE100DD57A1 /* orderby_spec_test.json */; }; 479A392EAB42453D49435D28 /* memory_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB4AB1388538CD3CB19EB028 /* memory_bundle_cache_test.cc */; }; 47B8ED6737A24EF96B1ED318 /* garbage_collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = AAED89D7690E194EF3BA1132 /* garbage_collection_spec_test.json */; }; @@ -807,6 +815,7 @@ 688AC36AA9D0677E910D5A37 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; 6938575C8B5E6FE0D562547A /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; }; 6938ABD1891AD4B9FD5FE664 /* document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */; }; + 6955586A4C34390290B97CED /* array_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0458BABD8F8738AD16F4A2FE /* array_test.cc */; }; 69D3AD697D1A7BF803A08160 /* field_index_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BF76A8DA34B5B67B4DD74666 /* field_index_test.cc */; }; 69ED7BC38B3F981DE91E7933 /* strerror_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 358C3B5FE573B1D60A4F7592 /* strerror_test.cc */; }; 6A40835DB2C02B9F07C02E88 /* field_mask_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5320A36E1F00BCEB75 /* field_mask_test.cc */; }; @@ -815,6 +824,7 @@ 6ABB82D43C0728EB095947AF /* geo_point_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB7BAB332012B519001E0872 /* geo_point_test.cc */; }; 6AED40FF444F0ACFE3AE96E3 /* target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B5C37696557C81A6C2B7271A /* target_cache_test.cc */; }; 6AF739DDA9D33DF756DE7CDE /* autoid_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A521FC913E500713A1A /* autoid_test.cc */; }; + 6B2CE342D89EDBE78CF46454 /* field_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 24F0F49F016E65823E0075DB /* field_test.cc */; }; 6B8E8B6C9EFDB3F1F91628A0 /* Validation_BloomFilterTest_MD5_5000_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 57F8EE51B5EFC9FAB185B66C /* Validation_BloomFilterTest_MD5_5000_01_bloom_filter_proto.json */; }; 6B94E0AE1002C5C9EA0F5582 /* log_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54C2294E1FECABAE007D065B /* log_test.cc */; }; 6BA8753F49951D7AEAD70199 /* watch_change_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2D7472BC70C024D736FF74D9 /* watch_change_test.cc */; }; @@ -823,6 +833,7 @@ 6C388B2D0967088758FF2425 /* leveldb_target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E76F0CDF28E5FA62D21DE648 /* leveldb_target_cache_test.cc */; }; 6C415868AE347DC4A26588C3 /* Validation_BloomFilterTest_MD5_500_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = D22D4C211AC32E4F8B4883DA /* Validation_BloomFilterTest_MD5_500_0001_bloom_filter_proto.json */; }; 6C92AD45A3619A18ECCA5B1F /* query_listener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7C3F995E040E9E9C5E8514BB /* query_listener_test.cc */; }; + 6C941147D9DB62E1A845CAB7 /* debug_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F6DBD8EDF0074DD0079ECCE6 /* debug_test.cc */; }; 6D578695E8E03988820D401C /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CFC201A2EE200D97691 /* string_util_test.cc */; }; 6D7F70938662E8CA334F11C2 /* target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B5C37696557C81A6C2B7271A /* target_cache_test.cc */; }; 6DBB3DB3FD6B4981B7F26A55 /* FIRQuerySnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04F202154AA00B64F25 /* FIRQuerySnapshotTests.mm */; }; @@ -871,6 +882,8 @@ 731541612214AFFA0037F4DC /* query_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 731541602214AFFA0037F4DC /* query_spec_test.json */; }; 733AFC467B600967536BD70F /* BasicCompileTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0761F61F2FE68D003233AF /* BasicCompileTests.swift */; }; 734DAB5FD6FEB2B219CEA8AD /* byte_stream_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7628664347B9C96462D4BF17 /* byte_stream_apple_test.mm */; }; + 735410A8B14BA0CF00526179 /* debug_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F6DBD8EDF0074DD0079ECCE6 /* debug_test.cc */; }; + 736B1B4D75F56314071987A1 /* array_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0458BABD8F8738AD16F4A2FE /* array_test.cc */; }; 736C4E82689F1CA1859C4A3F /* XCTestCase+Await.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0372021401E00B64F25 /* XCTestCase+Await.mm */; }; 73866AA12082B0A5009BB4FF /* FIRArrayTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 73866A9F2082B069009BB4FF /* FIRArrayTransformTests.mm */; }; 7394B5C29C6E524C2AF964E6 /* counting_query_engine.cc in Sources */ = {isa = PBXBuildFile; fileRef = 99434327614FEFF7F7DC88EC /* counting_query_engine.cc */; }; @@ -895,6 +908,7 @@ 77D38E78F7CCB8504450A8FB /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; }; 77D3CF0BE43BC67B9A26B06D /* FIRFieldPathTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04C202154AA00B64F25 /* FIRFieldPathTests.mm */; }; 7801E06BFFB08FCE7AB54AD6 /* thread_safe_memoizer_testing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EA10515F99A42D71DA2D2841 /* thread_safe_memoizer_testing_test.cc */; }; + 781E6608FCD77F3E9B3D19AE /* field_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 24F0F49F016E65823E0075DB /* field_test.cc */; }; 784FCB02C76096DACCBA11F2 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; }; 78D99CDBB539B0AEE0029831 /* Validation_BloomFilterTest_MD5_50000_1_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 3841925AA60E13A027F565E6 /* Validation_BloomFilterTest_MD5_50000_1_membership_test_result.json */; }; 78E8DDDBE131F3DA9AF9F8B8 /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; }; @@ -1019,6 +1033,7 @@ 8D0EF43F1B7B156550E65C20 /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; }; 8D67BAAD6D2F1913BACA6AC1 /* thread_safe_memoizer_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6E42FA109D363EA7F3387AAE /* thread_safe_memoizer_testing.cc */; }; 8DBA8DC55722ED9D3A1BB2C9 /* Validation_BloomFilterTest_MD5_5000_1_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 1A7D48A017ECB54FD381D126 /* Validation_BloomFilterTest_MD5_5000_1_membership_test_result.json */; }; + 8DD012A04D143ABDBA86340D /* logical_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51619F8CFF13B0CDD13EDC3 /* logical_test.cc */; }; 8E103A426D6E650DC338F281 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = C8FB22BCB9F454DA44BA80C8 /* Validation_BloomFilterTest_MD5_50000_01_membership_test_result.json */; }; 8E41D53C77C30372840B0367 /* Validation_BloomFilterTest_MD5_5000_0001_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 728F617782600536F2561463 /* Validation_BloomFilterTest_MD5_5000_0001_bloom_filter_proto.json */; }; 8ECDF2AFCF1BCA1A2CDAAD8A /* document_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB6B908320322E4D00CC290A /* document_test.cc */; }; @@ -1101,6 +1116,7 @@ 9E656F4FE92E8BFB7F625283 /* to_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B696858D2214B53900271095 /* to_string_test.cc */; }; 9EE1447AA8E68DF98D0590FF /* precondition_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5520A36E1F00BCEB75 /* precondition_test.cc */; }; 9EE81B1FB9B7C664B7B0A904 /* resume_token_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A41F315EE100DD57A1 /* resume_token_spec_test.json */; }; + 9F39F764F6AB575F890FD731 /* field_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 24F0F49F016E65823E0075DB /* field_test.cc */; }; 9F41D724D9947A89201495AD /* limit_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129F1F315EE100DD57A1 /* limit_spec_test.json */; }; 9F9244225BE2EC88AA0CE4EF /* sorted_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4C20A36DBB00BCEB75 /* sorted_set_test.cc */; }; A05BC6BDA2ABE405009211A9 /* target_id_generator_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CF82019382300D97691 /* target_id_generator_test.cc */; }; @@ -1223,6 +1239,7 @@ B2554A2BA211D10823646DBE /* Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4BD051DBE754950FEAC7A446 /* Validation_BloomFilterTest_MD5_500_01_bloom_filter_proto.json */; }; B28ACC69EB1F232AE612E77B /* async_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 872C92ABD71B12784A1C5520 /* async_testing.cc */; }; B2A9965ED0114E39A911FD09 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4375BDCDBCA9938C7F086730 /* Validation_BloomFilterTest_MD5_5000_1_bloom_filter_proto.json */; }; + B2B6347B9AD226204195AE3F /* debug_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F6DBD8EDF0074DD0079ECCE6 /* debug_test.cc */; }; B31B5E0D4EA72C5916CC71F5 /* thread_safe_memoizer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1A8141230C7E3986EACEF0B6 /* thread_safe_memoizer_test.cc */; }; B371628DA91E80B64AE53085 /* FIRFieldPathTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04C202154AA00B64F25 /* FIRFieldPathTests.mm */; }; B384E0F90D4CCC15C88CAF30 /* target_index_matcher_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 63136A2371C0C013EC7A540C /* target_index_matcher_test.cc */; }; @@ -1291,6 +1308,7 @@ BAB43C839445782040657239 /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; }; BACA9CDF0F2E926926B5F36F /* collection_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4B0A3187AAD8B02135E80C2E /* collection_test.cc */; }; BACBBF4AF2F5455673AEAB35 /* leveldb_migrations_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = EF83ACD5E1E9F25845A9ACED /* leveldb_migrations_test.cc */; }; + BB07838C0EAB5E32CD0C75C6 /* logical_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51619F8CFF13B0CDD13EDC3 /* logical_test.cc */; }; BB15588CC1622904CF5AD210 /* sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */; }; BB1A6F7D8F06E74FB6E525C5 /* document_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6152AD5202A5385000E5744 /* document_key_test.cc */; }; BB3F35B1510FE5449E50EC8A /* bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F7FC06E0A47D393DE1759AE1 /* bundle_cache_test.cc */; }; @@ -1315,6 +1333,7 @@ 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 */; }; + BE869F90074A4B0B948A3D65 /* debug_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F6DBD8EDF0074DD0079ECCE6 /* debug_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 */; }; BEF0365AD2718B8B70715978 /* statusor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352D20A3B3D7003E0143 /* statusor_test.cc */; }; @@ -1393,6 +1412,7 @@ CBDCA7829AAFEB4853C15517 /* bundle_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B5C2A94EE24E60543F62CC35 /* bundle_serializer_test.cc */; }; CC94A33318F983907E9ED509 /* resume_token_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A41F315EE100DD57A1 /* resume_token_spec_test.json */; }; CCE596E8654A4D2EEA75C219 /* index_backfiller_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1F50E872B3F117A674DA8E94 /* index_backfiller_test.cc */; }; + CCFA5699E41CD3EA00E30B52 /* array_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0458BABD8F8738AD16F4A2FE /* array_test.cc */; }; CD1E2F356FC71D7E74FCD26C /* leveldb_remote_document_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0840319686A223CC4AD3FAB1 /* leveldb_remote_document_cache_test.cc */; }; CD226D868CEFA9D557EF33A1 /* query_listener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7C3F995E040E9E9C5E8514BB /* query_listener_test.cc */; }; CD76A9EBD2E7D9E9E35A04F7 /* memory_globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C6DEA63FBDE19D841291723 /* memory_globals_cache_test.cc */; }; @@ -1412,6 +1432,7 @@ D04CBBEDB8DC16D8C201AC49 /* leveldb_target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E76F0CDF28E5FA62D21DE648 /* leveldb_target_cache_test.cc */; }; D0CD302D79FF5CE4F418FF0E /* FSTExceptionCatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = B8BFD9B37D1029D238BDD71E /* FSTExceptionCatcher.m */; }; D0DA42DC66C4FE508A63B269 /* testing_hooks_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A002425BC4FC4E805F4175B6 /* testing_hooks_test.cc */; }; + D1137289F2C00FFC66CE1CF7 /* field_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 24F0F49F016E65823E0075DB /* field_test.cc */; }; D143FBD057481C1A59B27E5E /* persistence_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A31F315EE100DD57A1 /* persistence_spec_test.json */; }; D156B9F19B5B29E77664FDFC /* logic_utils_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28B45B2104E2DAFBBF86DBB7 /* logic_utils_test.cc */; }; D1690214781198276492442D /* event_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F57521E161450FAF89075ED /* event_manager_test.cc */; }; @@ -1474,6 +1495,7 @@ DB3ADDA51FB93E84142EA90D /* FIRBundlesTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 776530F066E788C355B78457 /* FIRBundlesTests.mm */; }; DB7E9C5A59CCCDDB7F0C238A /* path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 403DBF6EFB541DFD01582AA3 /* path_test.cc */; }; DBDC8E997E909804F1B43E92 /* log_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54C2294E1FECABAE007D065B /* log_test.cc */; }; + DBF2E95F2EA837033E4A0528 /* array_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0458BABD8F8738AD16F4A2FE /* array_test.cc */; }; DBFE8B2E803C1D0DECB71FF6 /* FIRTransactionOptionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF39ECA1293D21A0A2AB2626 /* FIRTransactionOptionsTests.mm */; }; 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 */; }; @@ -1560,6 +1582,7 @@ E8495A8D1E11C0844339CCA3 /* database_info_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D92E20235D22000A432D /* database_info_test.cc */; }; E8608D40B683938C6D785627 /* credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2F4FA4576525144C5069A7A5 /* credentials_provider_test.cc */; }; E884336B43BBD1194C17E3C4 /* status_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3CAA33F964042646FDDAF9F9 /* status_testing.cc */; }; + E8911F2BCC97B0B1075D227B /* logical_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51619F8CFF13B0CDD13EDC3 /* logical_test.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 */; }; @@ -1633,6 +1656,7 @@ F272A8C41D2353700A11D1FB /* field_mask_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5320A36E1F00BCEB75 /* field_mask_test.cc */; }; F27347560A963E8162C56FF3 /* target_index_matcher_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 63136A2371C0C013EC7A540C /* target_index_matcher_test.cc */; }; F2876F16CF689FD7FFBA9DFA /* Validation_BloomFilterTest_MD5_1_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 0D964D4936953635AC7E0834 /* Validation_BloomFilterTest_MD5_1_01_bloom_filter_proto.json */; }; + F29C8C24164706138830F3E0 /* array_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0458BABD8F8738AD16F4A2FE /* array_test.cc */; }; F2AB7EACA1B9B1A7046D3995 /* FSTSyncEngineTestDriver.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02E20213FFC00B64F25 /* FSTSyncEngineTestDriver.mm */; }; F2F644E64B5FC82711DE70D7 /* FSTTestingHooks.mm in Sources */ = {isa = PBXBuildFile; fileRef = D85AC18C55650ED230A71B82 /* FSTTestingHooks.mm */; }; F3261CBFC169DB375A0D9492 /* FSTMockDatastore.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02D20213FFC00B64F25 /* FSTMockDatastore.mm */; }; @@ -1759,6 +1783,7 @@ 014C60628830D95031574D15 /* random_access_queue_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = random_access_queue_test.cc; sourceTree = ""; }; 01D10113ECC5B446DB35E96D /* byte_stream_cpp_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = byte_stream_cpp_test.cc; sourceTree = ""; }; 03BD47161789F26754D3B958 /* Pods-Firestore_Benchmarks_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Benchmarks_iOS.release.xcconfig"; path = "Target Support Files/Pods-Firestore_Benchmarks_iOS/Pods-Firestore_Benchmarks_iOS.release.xcconfig"; sourceTree = ""; }; + 0458BABD8F8738AD16F4A2FE /* array_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = array_test.cc; path = expressions/array_test.cc; sourceTree = ""; }; 045D39C4A7D52AF58264240F /* remote_document_cache_test.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = remote_document_cache_test.h; sourceTree = ""; }; 0473AFFF5567E667A125347B /* ordered_code_benchmark.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = ordered_code_benchmark.cc; sourceTree = ""; }; 062072B62773A055001655D7 /* AsyncAwaitIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncAwaitIntegrationTests.swift; sourceTree = ""; }; @@ -1786,6 +1811,7 @@ 1F78CD3208A1D5885B4C134E /* field_behavior.pb.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = field_behavior.pb.cc; sourceTree = ""; }; 214877F52A705012D6720CA0 /* object_value_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = object_value_test.cc; sourceTree = ""; }; 2286F308EFB0534B1BDE05B9 /* memory_target_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_target_cache_test.cc; sourceTree = ""; }; + 24F0F49F016E65823E0075DB /* field_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = field_test.cc; path = expressions/field_test.cc; sourceTree = ""; }; 25191D04F1D477571A7D3740 /* Pods-Firestore_Benchmarks_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Benchmarks_iOS.debug.xcconfig"; path = "Target Support Files/Pods-Firestore_Benchmarks_iOS/Pods-Firestore_Benchmarks_iOS.debug.xcconfig"; sourceTree = ""; }; 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 = ""; }; @@ -2193,8 +2219,10 @@ F119BDDF2F06B3C0883B8297 /* firebase_app_check_credentials_provider_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; name = firebase_app_check_credentials_provider_test.mm; path = credentials/firebase_app_check_credentials_provider_test.mm; sourceTree = ""; }; F243090EDC079930C87D5F96 /* Pods-Firestore_Tests_tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_tvOS.debug.xcconfig"; path = "Target Support Files/Pods-Firestore_Tests_tvOS/Pods-Firestore_Tests_tvOS.debug.xcconfig"; sourceTree = ""; }; F339B5B848F79BBDB2133210 /* Pods-Firestore_Example_tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Example_tvOS.release.xcconfig"; path = "Target Support Files/Pods-Firestore_Example_tvOS/Pods-Firestore_Example_tvOS.release.xcconfig"; sourceTree = ""; }; + F51619F8CFF13B0CDD13EDC3 /* logical_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = logical_test.cc; path = expressions/logical_test.cc; sourceTree = ""; }; F51859B394D01C0C507282F1 /* filesystem_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = filesystem_test.cc; sourceTree = ""; }; F6CA0C5638AB6627CB5B4CF4 /* memory_local_store_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_local_store_test.cc; sourceTree = ""; }; + F6DBD8EDF0074DD0079ECCE6 /* debug_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = debug_test.cc; path = expressions/debug_test.cc; sourceTree = ""; }; F7FC06E0A47D393DE1759AE1 /* bundle_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = bundle_cache_test.cc; sourceTree = ""; }; F8043813A5D16963EC02B182 /* local_serializer_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = local_serializer_test.cc; sourceTree = ""; }; F848C41C03A25C42AD5A4BC2 /* target_cache_test.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = target_cache_test.h; sourceTree = ""; }; @@ -3018,7 +3046,11 @@ isa = PBXGroup; children = ( 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */, + 0458BABD8F8738AD16F4A2FE /* array_test.cc */, 87DD1A65EBA9FFC1FFAAE657 /* comparison_test.cc */, + F6DBD8EDF0074DD0079ECCE6 /* debug_test.cc */, + 24F0F49F016E65823E0075DB /* field_test.cc */, + F51619F8CFF13B0CDD13EDC3 /* logical_test.cc */, ); name = expressions; sourceTree = ""; @@ -4298,6 +4330,7 @@ FF3405218188DFCE586FB26B /* app_testing.mm in Sources */, E8BB7CCF3928A5866B1C9B86 /* arithmetic_test.cc in Sources */, B192F30DECA8C28007F9B1D0 /* array_sorted_map_test.cc in Sources */, + CCFA5699E41CD3EA00E30B52 /* array_test.cc in Sources */, 4F857404731D45F02C5EE4C3 /* async_queue_libdispatch_test.mm in Sources */, 83A9CD3B6E791A860CE81FA1 /* async_queue_std_test.cc in Sources */, 0B7B24194E2131F5C325FE0E /* async_queue_test.cc in Sources */, @@ -4319,14 +4352,15 @@ 9AC604BF7A76CABDF26F8C8E /* cc_compilation_test.cc in Sources */, 1B730A4E8C4BD7B5B0FF9C7F /* collection_test.cc in Sources */, 5556B648B9B1C2F79A706B4F /* common.pb.cc in Sources */, - 11627F3A48F710D654829807 /* comparison_test.cc in Sources */, 08D853C9D3A4DC919C55671A /* comparison_test.cc in Sources */, + 11627F3A48F710D654829807 /* 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 */, 9774A6C2AA02A12D80B34C3C /* database_id_test.cc in Sources */, 11F8EE69182C9699E90A9E3D /* database_info_test.cc in Sources */, E2B7AEDCAAC5AD74C12E85C1 /* datastore_test.cc in Sources */, + BE869F90074A4B0B948A3D65 /* debug_test.cc in Sources */, 5E7812753D960FBB373435BD /* defer_test.cc in Sources */, 62DA31B79FE97A90EEF28B0B /* delayed_constructor_test.cc in Sources */, FF4FA5757D13A2B7CEE40F04 /* document.pb.cc in Sources */, @@ -4347,6 +4381,7 @@ 2E373EA9D5FF8C6DE2507675 /* field_index_test.cc in Sources */, 07B1E8C62772758BC82FEBEE /* field_mask_test.cc in Sources */, D9366A834BFF13246DC3AF9E /* field_path_test.cc in Sources */, + 1618D290DC26C76A1F0C87D7 /* field_test.cc in Sources */, C961FA581F87000DF674BBC8 /* field_transform_test.cc in Sources */, 4EC642DFC4AE98DBFFB37B17 /* fields_array_test.cc in Sources */, 60C72F86D2231B1B6592A5E6 /* filesystem_test.cc in Sources */, @@ -4393,6 +4428,7 @@ C23552A6D9FB0557962870C2 /* local_store_test.cc in Sources */, DBDC8E997E909804F1B43E92 /* log_test.cc in Sources */, F924DF3D9DCD2720C315A372 /* logic_utils_test.cc in Sources */, + 477D5B6AB66340FEA10B6D23 /* logical_test.cc in Sources */, 3F6C9F8A993CF4B0CD51E7F0 /* lru_garbage_collector_test.cc in Sources */, 1F6319D85C1AFC0D81394470 /* maybe_document.pb.cc in Sources */, 380E543B7BC6F648BBB250B4 /* md5_test.cc in Sources */, @@ -4528,6 +4564,7 @@ 6EEA00A737690EF82A3C91C6 /* app_testing.mm in Sources */, 033A1FECDD47ED9B1891093B /* arithmetic_test.cc in Sources */, 1291D9F5300AFACD1FBD262D /* array_sorted_map_test.cc in Sources */, + 736B1B4D75F56314071987A1 /* array_test.cc in Sources */, 4AD9809C9CE9FA09AC40992F /* async_queue_libdispatch_test.mm in Sources */, 38208AC761FF994BA69822BE /* async_queue_std_test.cc in Sources */, 900D0E9F18CE3DB954DD0D1E /* async_queue_test.cc in Sources */, @@ -4549,14 +4586,15 @@ 079E63E270F3EFCA175D2705 /* cc_compilation_test.cc in Sources */, 0480559E91BB66732ABE45C8 /* collection_test.cc in Sources */, 18638EAED9E126FC5D895B14 /* common.pb.cc in Sources */, - 6888F84253360455023C600B /* comparison_test.cc in Sources */, 1115DB1F1DCE93B63E03BA8C /* comparison_test.cc in Sources */, + 6888F84253360455023C600B /* 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 */, 58E377DCCC64FE7D2C6B59A1 /* database_id_test.cc in Sources */, 8F3AE423677A4C50F7E0E5C0 /* database_info_test.cc in Sources */, 9A7CF567C6FF0623EB4CFF64 /* datastore_test.cc in Sources */, + 37664236439C338A73A984B9 /* debug_test.cc in Sources */, 17DC97DE15D200932174EC1F /* defer_test.cc in Sources */, D22B96C19A0F3DE998D4320C /* delayed_constructor_test.cc in Sources */, 25A75DFA730BAD21A5538EC5 /* document.pb.cc in Sources */, @@ -4577,6 +4615,7 @@ 69D3AD697D1A7BF803A08160 /* field_index_test.cc in Sources */, ED4E2AC80CAF2A8FDDAC3DEE /* field_mask_test.cc in Sources */, 41EAC526C543064B8F3F7EDA /* field_path_test.cc in Sources */, + D1137289F2C00FFC66CE1CF7 /* field_test.cc in Sources */, A192648233110B7B8BD65528 /* field_transform_test.cc in Sources */, E99D5467483B746D4AA44F74 /* fields_array_test.cc in Sources */, AAF2F02E77A80C9CDE2C0C7A /* filesystem_test.cc in Sources */, @@ -4623,6 +4662,7 @@ 0C4219F37CC83614F1FD44ED /* local_store_test.cc in Sources */, 12BB9ED1CA98AA52B92F497B /* log_test.cc in Sources */, 7EF56BA2A480026D62CCA35A /* logic_utils_test.cc in Sources */, + E8911F2BCC97B0B1075D227B /* logical_test.cc in Sources */, 1F56F51EB6DF0951B1F4F85B /* lru_garbage_collector_test.cc in Sources */, DD175F74AC25CC419E874A1D /* maybe_document.pb.cc in Sources */, DCC8F3D4AA87C81AB3FD9491 /* md5_test.cc in Sources */, @@ -4784,6 +4824,7 @@ 7B8D7BAC1A075DB773230505 /* app_testing.mm in Sources */, 8976F3D5515C4A784EC6627F /* arithmetic_test.cc in Sources */, DC1C711290E12F8EF3601151 /* array_sorted_map_test.cc in Sources */, + 3B229A902E93497D4B559F80 /* array_test.cc in Sources */, 9B2CD4CBB1DFE8BC3C81A335 /* async_queue_libdispatch_test.mm in Sources */, 342724CA250A65E23CB133AC /* async_queue_std_test.cc in Sources */, DA1D665B12AA1062DCDEA6BD /* async_queue_test.cc in Sources */, @@ -4813,6 +4854,7 @@ 1465E362F7BA7A3D063E61C7 /* database_id_test.cc in Sources */, A8AF92A35DFA30EEF9C27FB7 /* database_info_test.cc in Sources */, B99452AB7E16B72D1C01FBBC /* datastore_test.cc in Sources */, + B2B6347B9AD226204195AE3F /* debug_test.cc in Sources */, 6325D0E43A402BC5866C9C0E /* defer_test.cc in Sources */, 2ABA80088D70E7A58F95F7D8 /* delayed_constructor_test.cc in Sources */, 1F38FD2703C58DFA69101183 /* document.pb.cc in Sources */, @@ -4833,6 +4875,7 @@ F8BD2F61EFA35C2D5120D9EB /* field_index_test.cc in Sources */, F272A8C41D2353700A11D1FB /* field_mask_test.cc in Sources */, AF6D6C47F9A25C65BFDCBBA0 /* field_path_test.cc in Sources */, + 9F39F764F6AB575F890FD731 /* field_test.cc in Sources */, B667366CB06893DFF472902E /* field_transform_test.cc in Sources */, 7B8320F12E8092BC86FFCC2C /* fields_array_test.cc in Sources */, D6486C7FFA8BE6F9C7D2F4C4 /* filesystem_test.cc in Sources */, @@ -4879,6 +4922,7 @@ EE470CC3C8FBCDA5F70A8466 /* local_store_test.cc in Sources */, CAFB1E0ED514FEF4641E3605 /* log_test.cc in Sources */, 0595B5EBEB8F09952B72C883 /* logic_utils_test.cc in Sources */, + 8DD012A04D143ABDBA86340D /* logical_test.cc in Sources */, 913F6E57AF18F84C5ECFD414 /* lru_garbage_collector_test.cc in Sources */, 27B652E6288A9CD1B99E618F /* maybe_document.pb.cc in Sources */, 13ED75EFC2F6917951518A4B /* md5_test.cc in Sources */, @@ -5040,6 +5084,7 @@ 8F4F40E9BC7ED588F67734D5 /* app_testing.mm in Sources */, BE4C2DFCEEFDC1DC0B37533D /* arithmetic_test.cc in Sources */, A6E236CE8B3A47BE32254436 /* array_sorted_map_test.cc in Sources */, + F29C8C24164706138830F3E0 /* array_test.cc in Sources */, 1CB8AEFBF3E9565FF9955B50 /* async_queue_libdispatch_test.mm in Sources */, AB2BAB0BD77FF05CC26FCF75 /* async_queue_std_test.cc in Sources */, 2FA0BAE32D587DF2EA5EEB97 /* async_queue_test.cc in Sources */, @@ -5069,6 +5114,7 @@ 1D618761796DE311A1707AA2 /* database_id_test.cc in Sources */, E8495A8D1E11C0844339CCA3 /* database_info_test.cc in Sources */, 7B74447D211586D9D1CC82BB /* datastore_test.cc in Sources */, + 6C941147D9DB62E1A845CAB7 /* debug_test.cc in Sources */, A6A9946A006AA87240B37E31 /* defer_test.cc in Sources */, 4EE1ABA574FBFDC95165624C /* delayed_constructor_test.cc in Sources */, E27C0996AF6EC6D08D91B253 /* document.pb.cc in Sources */, @@ -5089,6 +5135,7 @@ 50C852E08626CFA7DC889EEA /* field_index_test.cc in Sources */, A1563EFEB021936D3FFE07E3 /* field_mask_test.cc in Sources */, B235E260EA0DCB7BAC04F69B /* field_path_test.cc in Sources */, + 781E6608FCD77F3E9B3D19AE /* field_test.cc in Sources */, 1BF1F9A0CBB6B01654D3C2BE /* field_transform_test.cc in Sources */, E15A05789FF01F44BCAE75EF /* fields_array_test.cc in Sources */, 199B778D5820495797E0BE02 /* filesystem_test.cc in Sources */, @@ -5135,6 +5182,7 @@ DF4B3835C5AA4835C01CD255 /* local_store_test.cc in Sources */, 6B94E0AE1002C5C9EA0F5582 /* log_test.cc in Sources */, 0D6AE96565603226DB2E6838 /* logic_utils_test.cc in Sources */, + BB07838C0EAB5E32CD0C75C6 /* logical_test.cc in Sources */, 95CE3F5265B9BB7297EE5A6B /* lru_garbage_collector_test.cc in Sources */, 4F88E2D686CF4C150A29E84E /* maybe_document.pb.cc in Sources */, 211A60ECA3976D27C0BF59BB /* md5_test.cc in Sources */, @@ -5280,6 +5328,7 @@ 5467FB08203E6A44009C9584 /* app_testing.mm in Sources */, D4E02FF9F4D517BF5D4F2D14 /* arithmetic_test.cc in Sources */, 54EB764D202277B30088B8F3 /* array_sorted_map_test.cc in Sources */, + 6955586A4C34390290B97CED /* array_test.cc in Sources */, B6FB4684208EA0EC00554BA2 /* async_queue_libdispatch_test.mm in Sources */, B6FB4685208EA0F000554BA2 /* async_queue_std_test.cc in Sources */, B6FB467D208E9D3C00554BA2 /* async_queue_test.cc in Sources */, @@ -5301,14 +5350,15 @@ 08A9C531265B5E4C5367346E /* cc_compilation_test.cc in Sources */, C551536B0BAE9EB452DD6758 /* collection_test.cc in Sources */, 544129DA21C2DDC800EFB9CC /* common.pb.cc in Sources */, - 95490163C98C4F8AFD019730 /* comparison_test.cc in Sources */, 548DB929200D59F600E00ABC /* comparison_test.cc in Sources */, + 95490163C98C4F8AFD019730 /* 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 */, ABE6637A201FA81900ED349A /* database_id_test.cc in Sources */, AB38D93020236E21000A432D /* database_info_test.cc in Sources */, D3B470C98ACFAB7307FB3800 /* datastore_test.cc in Sources */, + 735410A8B14BA0CF00526179 /* debug_test.cc in Sources */, 26C4E52128C8E7B5B96BECC4 /* defer_test.cc in Sources */, 6EC28BB8C38E3FD126F68211 /* delayed_constructor_test.cc in Sources */, 544129DD21C2DDC800EFB9CC /* document.pb.cc in Sources */, @@ -5329,6 +5379,7 @@ 03AEB9E07A605AE1B5827548 /* field_index_test.cc in Sources */, 549CCA5720A36E1F00BCEB75 /* field_mask_test.cc in Sources */, B686F2AF2023DDEE0028D6BE /* field_path_test.cc in Sources */, + 6B2CE342D89EDBE78CF46454 /* field_test.cc in Sources */, 2EC1C4D202A01A632339A161 /* field_transform_test.cc in Sources */, B6DD950022FBEA28EF9BE463 /* fields_array_test.cc in Sources */, D94A1862B8FB778225DB54A1 /* filesystem_test.cc in Sources */, @@ -5375,6 +5426,7 @@ D21060F8115A5F48FC3BF335 /* local_store_test.cc in Sources */, 54C2294F1FECABAE007D065B /* log_test.cc in Sources */, D156B9F19B5B29E77664FDFC /* logic_utils_test.cc in Sources */, + 25202D64249BFE38AB8B8DA9 /* logical_test.cc in Sources */, 1290FA77A922B76503AE407C /* lru_garbage_collector_test.cc in Sources */, 85ADFEB234EBE3D9CDFFCE12 /* maybe_document.pb.cc in Sources */, C86E85101352B5CDBF5909F9 /* md5_test.cc in Sources */, @@ -5555,6 +5607,7 @@ EBFC611B1BF195D0EC710AF4 /* app_testing.mm in Sources */, 1792477DD2B3A1710BFD443F /* arithmetic_test.cc in Sources */, FCA48FB54FC50BFDFDA672CD /* array_sorted_map_test.cc in Sources */, + DBF2E95F2EA837033E4A0528 /* array_test.cc in Sources */, 45A5504D33D39C6F80302450 /* async_queue_libdispatch_test.mm in Sources */, 6F914209F46E6552B5A79570 /* async_queue_std_test.cc in Sources */, AD74843082C6465A676F16A7 /* async_queue_test.cc in Sources */, @@ -5584,6 +5637,7 @@ 61976CE9C088131EC564A503 /* database_id_test.cc in Sources */, 65FC1A102890C02EF1A65213 /* database_info_test.cc in Sources */, 4D6761FB02F4D915E466A985 /* datastore_test.cc in Sources */, + 25937E75A75B77DDA4D2FCF5 /* debug_test.cc in Sources */, 96898170B456EAF092F73BBC /* defer_test.cc in Sources */, C663A8B74B57FD84717DEA21 /* delayed_constructor_test.cc in Sources */, C426C6E424FB2199F5C2C5BC /* document.pb.cc in Sources */, @@ -5604,6 +5658,7 @@ 84285C3F63D916A4786724A8 /* field_index_test.cc in Sources */, 6A40835DB2C02B9F07C02E88 /* field_mask_test.cc in Sources */, D00E69F7FDF2BE674115AD3F /* field_path_test.cc in Sources */, + 1C12B0A8896ACAD736B5CDC7 /* field_test.cc in Sources */, 9016EF298E41456060578C90 /* field_transform_test.cc in Sources */, C437916821C90F04F903EB96 /* fields_array_test.cc in Sources */, 280A282BE9AF4DCF4E855EAB /* filesystem_test.cc in Sources */, @@ -5650,6 +5705,7 @@ A97ED2BAAEDB0F765BBD5F98 /* local_store_test.cc in Sources */, 677C833244550767B71DB1BA /* log_test.cc in Sources */, 6FCC64A1937E286E76C294D0 /* logic_utils_test.cc in Sources */, + 45070DD0F8428BB68E6895C6 /* logical_test.cc in Sources */, 4DF18D15AC926FB7A4888313 /* lru_garbage_collector_test.cc in Sources */, DC3351455F8753678905CF73 /* maybe_document.pb.cc in Sources */, E74D6C1056DE29969B5C4C62 /* md5_test.cc in Sources */, diff --git a/Firestore/core/src/core/expressions_eval.cc b/Firestore/core/src/core/expressions_eval.cc index 7ffe03440f1..04a687493a1 100644 --- a/Firestore/core/src/core/expressions_eval.cc +++ b/Firestore/core/src/core/expressions_eval.cc @@ -16,19 +16,24 @@ #include "Firestore/core/src/core/expressions_eval.h" +#include // For std::reverse #include #include #include #include // For std::move +#include // For std::vector #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/model/value_util.h" // For value helpers like IsArray, DeepClone +#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 +#include "Firestore/core/src/util/hard_assert.h" +#include "absl/types/optional.h" + +// Forward declarations for logical expression classes +#include "Firestore/core/src/core/expressions_eval.h" namespace firebase { namespace firestore { @@ -305,8 +310,48 @@ std::unique_ptr FunctionToEvaluable( return std::make_unique(function); } else if (function.name() == "gte") { return std::make_unique(function); + } else if (function.name() == "array_reverse") { // Removed array_concat + return std::make_unique(function); + } else if (function.name() == "array_contains") { + return std::make_unique(function); + } else if (function.name() == "array_contains_all") { + return std::make_unique(function); + } else if (function.name() == "array_contains_any") { + return std::make_unique(function); + } else if (function.name() == "array_length") { + return std::make_unique(function); + } else if (function.name() == "exists") { + return std::make_unique(function); + } else if (function.name() == "not") { + return std::make_unique(function); + } else if (function.name() == "and") { + return std::make_unique(function); + } else if (function.name() == "or") { + return std::make_unique(function); + } else if (function.name() == "xor") { + return std::make_unique(function); + } else if (function.name() == "cond") { + return std::make_unique(function); + } else if (function.name() == "eq_any") { + return std::make_unique(function); + } else if (function.name() == "not_eq_any") { + return std::make_unique(function); + } else if (function.name() == "is_nan") { + return std::make_unique(function); + } else if (function.name() == "is_not_nan") { + return std::make_unique(function); + } else if (function.name() == "is_null") { + return std::make_unique(function); + } else if (function.name() == "is_not_null") { + return std::make_unique(function); + } else if (function.name() == "is_error") { + return std::make_unique(function); + } else if (function.name() == "logical_maximum") { + return std::make_unique(function); + } else if (function.name() == "logical_minimum") { + return std::make_unique(function); } - // TODO(wuandy): Add other functions + // TODO(wuandy): Add other non-array/logical functions HARD_FAIL("Unsupported function name: %s", function.name()); } @@ -576,6 +621,729 @@ EvaluateResult CoreMod::Evaluate( }); } +// --- Array Expression Implementations --- + +EvaluateResult CoreArrayReverse::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 1, + "array_reverse() function requires exactly 1 param"); + + std::unique_ptr operand_evaluable = + expr_->params()[0]->ToEvaluable(); + EvaluateResult evaluated = operand_evaluable->Evaluate(context, document); + + switch (evaluated.type()) { + case EvaluateResult::ResultType::kNull: { + return EvaluateResult::NewNull(); + } + case EvaluateResult::ResultType::kArray: { + std::vector> reversed_values; + if (evaluated.value()->array_value.values != nullptr) { + for (pb_size_t i = 0; i < evaluated.value()->array_value.values_count; + ++i) { + // Deep clone each element to get a new FieldValue wrapper + reversed_values.push_back( + model::DeepClone(evaluated.value()->array_value.values[i])); + } + } + + std::reverse(reversed_values.begin(), reversed_values.end()); + return EvaluateResult::NewValue( + model::ArrayValue(std::move(reversed_values))); + } + default: + return EvaluateResult::NewError(); + } +} + +EvaluateResult CoreArrayContains::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 2, + "array_contains() function requires exactly 2 params"); + + std::vector> reversed_params( + expr_->params().rbegin(), expr_->params().rend()); + auto const eq_any = + CoreEqAny(api::FunctionExpr("eq_any", std::move(reversed_params))); + return eq_any.Evaluate(context, document); +} + +EvaluateResult CoreArrayContainsAll::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 2, + "array_contains_all() function requires exactly 2 params"); + + bool found_null = false; + + // Evaluate the array to search (param 0) + std::unique_ptr array_to_search_evaluable = + expr_->params()[0]->ToEvaluable(); + EvaluateResult array_to_search = + array_to_search_evaluable->Evaluate(context, document); + + switch (array_to_search.type()) { + case EvaluateResult::ResultType::kArray: { + break; // Expected type + } + case EvaluateResult::ResultType::kNull: { + found_null = true; + break; + } + default: { + return EvaluateResult::NewError(); // Error or Unset or wrong type + } + } + + // Evaluate the elements to find (param 1) + std::unique_ptr elements_to_find_evaluable = + expr_->params()[1]->ToEvaluable(); + EvaluateResult elements_to_find = + elements_to_find_evaluable->Evaluate(context, document); + + switch (elements_to_find.type()) { + case EvaluateResult::ResultType::kArray: { + break; // Expected type + } + case EvaluateResult::ResultType::kNull: { + found_null = true; + break; + } + default: { + // Handle all other types (kError, kUnset, kBoolean, kInt, kDouble, etc.) + // as errors for the 'elements_to_find' parameter. + return EvaluateResult::NewError(); + } + } + + // If either input was null, the result is null + if (found_null) { + return EvaluateResult::NewNull(); + } + + const google_firestore_v1_Value* search_values_proto = + elements_to_find.value(); + const google_firestore_v1_Value* array_values_proto = array_to_search.value(); + bool found_null_at_least_once = false; + + // Iterate through elements we need to find (search_values) + if (search_values_proto->array_value.values != nullptr) { + for (pb_size_t i = 0; i < search_values_proto->array_value.values_count; + ++i) { + const google_firestore_v1_Value& search = + search_values_proto->array_value.values[i]; + bool found = false; + + // Iterate through the array we are searching within (array_values) + if (array_values_proto->array_value.values != nullptr) { + for (pb_size_t j = 0; j < array_values_proto->array_value.values_count; + ++j) { + const google_firestore_v1_Value& value = + array_values_proto->array_value.values[j]; + + switch (model::StrictEquals(search, value)) { + case model::StrictEqualsResult::kEq: { + found = true; + break; // Found it, break inner loop + } + case model::StrictEqualsResult::kNotEq: { + // Keep searching + break; + } + case model::StrictEqualsResult::kNull: { + found_null = true; + found_null_at_least_once = true; // Track null globally + break; + } + } + if (found) { + break; // Exit inner loop once found + } + } // End inner loop (searching array_values) + } + + // Check result for the current 'search' element + if (found) { + // true case - do nothing, we found a match, make sure all other values + // are also found + } else { + // false case - we didn't find a match, short circuit + if (!found_null) { + return EvaluateResult::NewValue( + nanopb::MakeMessage(model::FalseValue())); + } + // null case - do nothing, we found at least one null value for this + // search element, keep going + } + } // End outer loop (iterating search_values) + } + + // If we finished the outer loop + if (found_null_at_least_once) { + // If we encountered any null comparison and didn't return false earlier, + // the result is null. + return EvaluateResult::NewNull(); + } else { + // If we finished and found no nulls, and never returned false, + // it means all elements were found. + return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue())); + } +} + +EvaluateResult CoreArrayContainsAny::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 2, + "array_contains_any() function requires exactly 2 params"); + + bool found_null = false; + + // Evaluate the array to search (param 0) + std::unique_ptr array_to_search_evaluable = + expr_->params()[0]->ToEvaluable(); + EvaluateResult array_to_search = + array_to_search_evaluable->Evaluate(context, document); + + switch (array_to_search.type()) { + case EvaluateResult::ResultType::kArray: { + break; // Expected type + } + case EvaluateResult::ResultType::kNull: { + found_null = true; + break; + } + default: { + return EvaluateResult::NewError(); // Error or Unset or wrong type + } + } + + // Evaluate the elements to find (param 1) + std::unique_ptr elements_to_find_evaluable = + expr_->params()[1]->ToEvaluable(); + EvaluateResult elements_to_find = + elements_to_find_evaluable->Evaluate(context, document); + + switch (elements_to_find.type()) { + case EvaluateResult::ResultType::kArray: { + break; // Expected type + } + case EvaluateResult::ResultType::kNull: { + found_null = true; + break; + } + default: { + // Handle all other types (kError, kUnset, kBoolean, kInt, kDouble, etc.) + // as errors for the 'elements_to_find' parameter. + return EvaluateResult::NewError(); + } + } + + // If either input was null, the result is null + if (found_null) { + return EvaluateResult::NewNull(); + } + + const google_firestore_v1_Value* search_values_proto = + elements_to_find.value(); + const google_firestore_v1_Value* array_values_proto = array_to_search.value(); + + // Outer loop: Iterate through the array being searched + if (search_values_proto->array_value.values != nullptr) { + for (pb_size_t i = 0; i < search_values_proto->array_value.values_count; + ++i) { + const google_firestore_v1_Value& candidate = + search_values_proto->array_value.values[i]; + + // Inner loop: Iterate through the elements to find + if (array_values_proto->array_value.values != nullptr) { + for (pb_size_t j = 0; j < array_values_proto->array_value.values_count; + ++j) { + const google_firestore_v1_Value& search_element = + array_values_proto->array_value.values[j]; + + switch (model::StrictEquals(candidate, search_element)) { + case model::StrictEqualsResult::kEq: { + // Found one match, return true immediately + return EvaluateResult::NewValue( + nanopb::MakeMessage(model::TrueValue())); + } + case model::StrictEqualsResult::kNotEq: + // Continue inner loop + break; + case model::StrictEqualsResult::kNull: + // Track null, continue inner loop + found_null = true; + break; + } + } // End inner loop + } + } // End outer loop + } + + // If we finished both loops without returning true + if (found_null) { + // If we encountered any null comparison, the result is null + return EvaluateResult::NewNull(); + } else { + // If no match was found and no nulls were encountered + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + } +} + +EvaluateResult CoreArrayLength::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 1, + "array_length() function requires exactly 1 param"); + + std::unique_ptr operand_evaluable = + expr_->params()[0]->ToEvaluable(); + EvaluateResult operand_result = + operand_evaluable->Evaluate(context, document); + + switch (operand_result.type()) { + case EvaluateResult::ResultType::kNull: { + return EvaluateResult::NewNull(); + } + case EvaluateResult::ResultType::kArray: { + size_t array_size = operand_result.value()->array_value.values_count; + return EvaluateResult::NewValue(IntValue(array_size)); + } + default: { + return EvaluateResult::NewError(); + } + } +} + +// --- Logical Expression Implementations --- + +// Constructor definitions removed as they are now inline in the header + +EvaluateResult CoreAnd::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + bool has_null = false; + bool has_error = false; + for (const auto& param : expr_->params()) { + EvaluateResult const result = + param->ToEvaluable()->Evaluate(context, document); + switch (result.type()) { + case EvaluateResult::ResultType::kBoolean: + if (!result.value()->boolean_value) { + // Short-circuit on false + return EvaluateResult::NewValue( + nanopb::MakeMessage(model::FalseValue())); + } + break; // Break if true + case EvaluateResult::ResultType::kNull: + has_null = true; // Track null, continue evaluation + break; + default: + has_error = true; + break; + } + } + + if (has_error) { + return EvaluateResult::NewError(); // If any operand results in error + } + + if (has_null) { + return EvaluateResult::NewNull(); // If null was encountered, result is + // null + } + + return EvaluateResult::NewValue( + nanopb::MakeMessage(model::TrueValue())); // Otherwise, result is true +} + +EvaluateResult CoreOr::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + bool has_null = false; + bool has_error = false; + for (const auto& param : expr_->params()) { + EvaluateResult const result = + param->ToEvaluable()->Evaluate(context, document); + switch (result.type()) { + case EvaluateResult::ResultType::kBoolean: + if (result.value()->boolean_value) { + // Short-circuit on true + return EvaluateResult::NewValue( + nanopb::MakeMessage(model::TrueValue())); + } + break; // Continue if false + case EvaluateResult::ResultType::kNull: + has_null = true; // Track null, continue evaluation + break; + default: + has_error = true; + break; + } + } + + // If loop completes without returning true: + if (has_error) { + return EvaluateResult::NewError(); + } + + if (has_null) { + return EvaluateResult::NewNull(); + } + + return EvaluateResult::NewValue( + nanopb::MakeMessage(model::FalseValue())); // Otherwise, result is false +} + +EvaluateResult CoreXor::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + bool current_xor_result = false; + bool has_null = false; + for (const auto& param : expr_->params()) { + EvaluateResult const evaluated = + param->ToEvaluable()->Evaluate(context, document); + switch (evaluated.type()) { + case EvaluateResult::ResultType::kBoolean: { + bool operand_value = evaluated.value()->boolean_value; + // XOR logic: result = result ^ operand + current_xor_result = current_xor_result != operand_value; + break; + } + case EvaluateResult::ResultType::kNull: { + has_null = true; + break; + } + default: { + // Any non-boolean, non-null operand results in error + return EvaluateResult::NewError(); + } + } + } + + if (has_null) { + return EvaluateResult::NewNull(); + } + return EvaluateResult::NewValue(nanopb::MakeMessage( + current_xor_result ? model::TrueValue() : model::FalseValue())); +} + +EvaluateResult CoreCond::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 3, + "cond() function requires exactly 3 params"); + + EvaluateResult condition = + expr_->params()[0]->ToEvaluable()->Evaluate(context, document); + + switch (condition.type()) { + case EvaluateResult::ResultType::kBoolean: { + if (condition.value()->boolean_value) { + // Condition is true, evaluate the second parameter + return expr_->params()[1]->ToEvaluable()->Evaluate(context, document); + } else { + // Condition is false, evaluate the third parameter + return expr_->params()[2]->ToEvaluable()->Evaluate(context, document); + } + } + case EvaluateResult::ResultType::kNull: { + // Condition is null, evaluate the third parameter (false case) + return expr_->params()[2]->ToEvaluable()->Evaluate(context, document); + } + default: + // Condition is error, unset, or non-boolean/non-null type + return EvaluateResult::NewError(); + } +} + +EvaluateResult CoreEqAny::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 2, + "eq_any() function requires exactly 2 params (search value and " + "array value)"); + + bool found_null = false; + + // Evaluate the search value (param 0) + EvaluateResult const search_result = + expr_->params()[0]->ToEvaluable()->Evaluate(context, document); + switch (search_result.type()) { + case EvaluateResult::ResultType::kNull: { + found_null = true; + break; + } + case EvaluateResult::ResultType::kError: + case EvaluateResult::ResultType::kUnset: + return EvaluateResult::NewError(); // Error/Unset search value is error + default: + break; // Valid value + } + + EvaluateResult const array_result = + expr_->params()[1]->ToEvaluable()->Evaluate(context, document); + switch (array_result.type()) { + case EvaluateResult::ResultType::kNull: { + found_null = true; + break; + } + case EvaluateResult::ResultType::kArray: { + break; + } + default: + return EvaluateResult::NewError(); + } + + if (found_null) { + return EvaluateResult::NewNull(); + } + + for (size_t i = 0; i < array_result.value()->array_value.values_count; ++i) { + const google_firestore_v1_Value& candidate = + array_result.value()->array_value.values[i]; + switch (model::StrictEquals(*search_result.value(), candidate)) { + case model::StrictEqualsResult::kEq: { + return EvaluateResult::NewValue( + nanopb::MakeMessage(model::TrueValue())); + } + case model::StrictEqualsResult::kNotEq: { + break; + } + case model::StrictEqualsResult::kNull: { + found_null = true; + break; + } + } + } + + if (found_null) { + return EvaluateResult::NewNull(); + } + + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); +} + +EvaluateResult CoreNotEqAny::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT( + expr_->params().size() == 2, + "not_eq_any() function requires exactly 2 params (search value and " + "array value)"); + + CoreNot equivalent(api::FunctionExpr( + "not", {std::make_shared("eq_any", expr_->params())})); + return equivalent.Evaluate(context, document); +} + +EvaluateResult CoreIsNan::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 1, + "is_nan() function requires exactly 1 param"); + + EvaluateResult evaluated = + expr_->params()[0]->ToEvaluable()->Evaluate(context, document); + switch (evaluated.type()) { + case EvaluateResult::ResultType::kInt: + // Integers are never NaN + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + case EvaluateResult::ResultType::kDouble: + // Check if the double value is NaN + return EvaluateResult::NewValue(nanopb::MakeMessage( + model::IsNaNValue(*evaluated.value()) ? model::TrueValue() + : model::FalseValue())); + case EvaluateResult::ResultType::kNull: + // is_nan(null) -> null + return EvaluateResult::NewNull(); + default: + // is_nan applied to non-numeric, non-null is an error + return EvaluateResult::NewError(); + } +} + +EvaluateResult CoreIsNotNan::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 1, + "is_not_nan() function requires exactly 1 param"); + + CoreNot equivalent(api::FunctionExpr( + "not", {std::make_shared("is_nan", expr_->params())})); + return equivalent.Evaluate(context, document); +} + +EvaluateResult CoreIsNull::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 1, + "is_null() function requires exactly 1 param"); + + EvaluateResult evaluated = + expr_->params()[0]->ToEvaluable()->Evaluate(context, document); + switch (evaluated.type()) { + case EvaluateResult::ResultType::kNull: + return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue())); + case EvaluateResult::ResultType::kUnset: + case EvaluateResult::ResultType::kError: + // is_null on error/unset is an error + return EvaluateResult::NewError(); + default: + // is_null on any other value is false + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + } +} + +EvaluateResult CoreIsNotNull::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 1, + "is_not_null() function requires exactly 1 param"); + + CoreNot equivalent(api::FunctionExpr( + "not", + {std::make_shared("is_null", expr_->params())})); + return equivalent.Evaluate(context, document); +} + +EvaluateResult CoreIsError::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 1, + "is_error() function requires exactly 1 param"); + + EvaluateResult evaluated = + expr_->params()[0]->ToEvaluable()->Evaluate(context, document); + switch (evaluated.type()) { + case EvaluateResult::ResultType::kError: + return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue())); + default: + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + } +} + +EvaluateResult CoreLogicalMaximum::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + // Store the underlying Value proto in the optional, not EvaluateResult + absl::optional> max_value_proto; + + for (const auto& param : expr_->params()) { + EvaluateResult result = param->ToEvaluable()->Evaluate(context, document); + + switch (result.type()) { + case EvaluateResult::ResultType::kError: + case EvaluateResult::ResultType::kUnset: + case EvaluateResult::ResultType::kNull: + // Skip null, error, unset + continue; + default: { + if (!max_value_proto.has_value() || + model::Compare(*result.value(), *max_value_proto.value()) == + util::ComparisonResult::Descending) { + // Store a deep copy of the value proto + max_value_proto = model::DeepClone(*result.value()); + } + } + } + } + + if (max_value_proto.has_value()) { + // Reconstruct EvaluateResult from the stored proto + return EvaluateResult::NewValue(std::move(max_value_proto.value())); + } + // If only null/error/unset were encountered, return Null + return EvaluateResult::NewNull(); +} + +EvaluateResult CoreLogicalMinimum::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + // Store the underlying Value proto in the optional, not EvaluateResult + absl::optional> min_value_proto; + + for (const auto& param : expr_->params()) { + EvaluateResult result = param->ToEvaluable()->Evaluate(context, document); + + switch (result.type()) { + case EvaluateResult::ResultType::kError: + case EvaluateResult::ResultType::kUnset: + case EvaluateResult::ResultType::kNull: + // Skip null, error, unset + continue; + default: { + if (!min_value_proto.has_value() || + model::Compare(*result.value(), *min_value_proto.value()) == + util::ComparisonResult::Ascending) { + min_value_proto = model::DeepClone(*result.value()); + } + } + } + } + + if (min_value_proto.has_value()) { + // Reconstruct EvaluateResult from the stored proto + return EvaluateResult::NewValue(std::move(min_value_proto.value())); + } + // If only null/error/unset were encountered, return Null + return EvaluateResult::NewNull(); +} + +// --- Debugging Expression Implementations --- + +EvaluateResult CoreExists::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 1, + "exists() function requires exactly 1 param"); + + std::unique_ptr operand_evaluable = + expr_->params()[0]->ToEvaluable(); + EvaluateResult evaluated = operand_evaluable->Evaluate(context, document); + + switch (evaluated.type()) { + case EvaluateResult::ResultType::kError: + return EvaluateResult::NewError(); // Propagate error + case EvaluateResult::ResultType::kUnset: + // Unset field means it doesn't exist + return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue())); + default: + // Null or any other value means it exists + return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue())); + } +} + +EvaluateResult CoreNot::Evaluate( + const api::EvaluateContext& context, + const model::PipelineInputOutput& document) const { + HARD_ASSERT(expr_->params().size() == 1, + "not() function requires exactly 1 param"); + + std::unique_ptr operand_evaluable = + expr_->params()[0]->ToEvaluable(); + EvaluateResult evaluated = operand_evaluable->Evaluate(context, document); + + switch (evaluated.type()) { + case EvaluateResult::ResultType::kBoolean: { + // Negate the boolean value + bool original_value = evaluated.value()->boolean_value; + return EvaluateResult::NewValue(nanopb::MakeMessage( + original_value ? model::FalseValue() : model::TrueValue())); + } + case EvaluateResult::ResultType::kNull: { + // NOT(NULL) -> NULL + return EvaluateResult::NewNull(); + } + default: { + // NOT applied to non-boolean, non-null is an error + return EvaluateResult::NewError(); + } + } +} + } // namespace core } // namespace firestore } // namespace firebase diff --git a/Firestore/core/src/core/expressions_eval.h b/Firestore/core/src/core/expressions_eval.h index 398cfd25c04..17a8eae7242 100644 --- a/Firestore/core/src/core/expressions_eval.h +++ b/Firestore/core/src/core/expressions_eval.h @@ -287,6 +287,272 @@ class CoreMod : public EvaluableExpr { std::unique_ptr expr_; }; +// --- Array Expressions --- + +class CoreArrayReverse : public EvaluableExpr { + public: + explicit CoreArrayReverse(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 CoreArrayContains : public EvaluableExpr { + public: + explicit CoreArrayContains(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 CoreArrayContainsAll : public EvaluableExpr { + public: + explicit CoreArrayContainsAll(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 CoreArrayContainsAny : public EvaluableExpr { + public: + explicit CoreArrayContainsAny(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 CoreArrayLength : public EvaluableExpr { + public: + explicit CoreArrayLength(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_; +}; + +// --- Logical Expressions --- + +class CoreAnd : public EvaluableExpr { + public: + explicit CoreAnd(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 CoreOr : public EvaluableExpr { + public: + explicit CoreOr(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 CoreXor : public EvaluableExpr { + public: + explicit CoreXor(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 CoreCond : public EvaluableExpr { + public: + explicit CoreCond(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 CoreEqAny : public EvaluableExpr { + public: + explicit CoreEqAny(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 CoreNotEqAny : public EvaluableExpr { + public: + explicit CoreNotEqAny(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 CoreIsNan : public EvaluableExpr { + public: + explicit CoreIsNan(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 CoreIsNotNan : public EvaluableExpr { + public: + explicit CoreIsNotNan(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 CoreIsNull : public EvaluableExpr { + public: + explicit CoreIsNull(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 CoreIsNotNull : public EvaluableExpr { + public: + explicit CoreIsNotNull(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 CoreIsError : public EvaluableExpr { + public: + explicit CoreIsError(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 CoreLogicalMaximum : public EvaluableExpr { + public: + explicit CoreLogicalMaximum(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 CoreLogicalMinimum : public EvaluableExpr { + public: + explicit CoreLogicalMinimum(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_; +}; + +// --- Debugging Expressions --- + +class CoreExists : public EvaluableExpr { + public: + explicit CoreExists(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 CoreNot : public EvaluableExpr { + public: + explicit CoreNot(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_; +}; + /** * Converts a high-level expression representation into an evaluable one. */ diff --git a/Firestore/core/src/model/value_util.cc b/Firestore/core/src/model/value_util.cc index cb7c7f0d45e..7b6a15732a2 100644 --- a/Firestore/core/src/model/value_util.cc +++ b/Firestore/core/src/model/value_util.cc @@ -955,6 +955,20 @@ Message RefValue( return result; } +Message ArrayValue( + std::vector> values) { + google_firestore_v1_Value result; + result.which_value_type = google_firestore_v1_Value_array_value_tag; + + SetRepeatedField(&result.array_value.values, &result.array_value.values_count, + values.begin(), values.end(), + [](Message& value) { + return *value.release(); + }); + + return nanopb::MakeMessage(result); +} + Message DeepClone( const google_firestore_v1_Value& source) { Message target{source}; diff --git a/Firestore/core/src/model/value_util.h b/Firestore/core/src/model/value_util.h index 4991acfbc58..12079e9498f 100644 --- a/Firestore/core/src/model/value_util.h +++ b/Firestore/core/src/model/value_util.h @@ -248,6 +248,14 @@ google_firestore_v1_Value MinMap(); nanopb::Message RefValue( const DatabaseId& database_id, const DocumentKey& document_key); +/** + * Returns a Protobuf array value representing the given values. + * + * This function owns the passed in vector and might move the values out. + */ +nanopb::Message ArrayValue( + std::vector> values); + /** Creates a copy of the contents of the Value proto. */ nanopb::Message DeepClone( const google_firestore_v1_Value& source); diff --git a/Firestore/core/test/unit/core/expressions/array_test.cc b/Firestore/core/test/unit/core/expressions/array_test.cc new file mode 100644 index 00000000000..dd77d14c2bb --- /dev/null +++ b/Firestore/core/test/unit/core/expressions/array_test.cc @@ -0,0 +1,375 @@ +/* + * 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 +#include // Required for quiet_NaN() +#include +#include + +#include "Firestore/core/src/api/expressions.h" // For api::Expr, api::Constant, api::Field +#include "Firestore/core/src/core/expressions_eval.h" +// #include "Firestore/core/src/model/field_value.h" // Removed incorrect +// include +#include "Firestore/core/src/model/value_util.h" // For value constants like NullValue, NaNValue +#include "Firestore/core/test/unit/testutil/expression_test_util.h" // For test helpers +#include "Firestore/core/test/unit/testutil/testutil.h" // For test helpers like Value, Array, Map +#include "gmock/gmock.h" // For matchers like Returns +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace core { + +using api::Expr; +// using model::FieldValue; // Removed incorrect using declaration +using testutil::Array; +using testutil::ArrayContainsAllExpr; +using testutil::ArrayContainsAnyExpr; +using testutil::ArrayContainsExpr; +using testutil::ArrayLengthExpr; +using testutil::Constant; // Use testutil::Constant for consistency +using testutil::EvaluateExpr; +using testutil::Field; +using testutil::Map; +using testutil::Returns; +using testutil::ReturnsError; +using testutil::ReturnsNull; +using testutil::ReturnsUnset; +using testutil::SharedConstant; +using testutil::Value; + +// Fixture for ArrayContainsAll function tests +class ArrayContainsAllTest : public ::testing::Test {}; + +// Fixture for ArrayContainsAny function tests +class ArrayContainsAnyTest : public ::testing::Test {}; + +// Fixture for ArrayContains function tests +class ArrayContainsTest : public ::testing::Test {}; + +// Fixture for ArrayLength function tests +class ArrayLengthTest : public ::testing::Test {}; + +// --- ArrayContainsAll Tests --- + +TEST_F(ArrayContainsAllTest, ContainsAll) { + EXPECT_THAT( + EvaluateExpr(*ArrayContainsAllExpr( + {SharedConstant(Array(Value("1"), Value(42LL), Value(true), + Value("additional"), Value("values"), + Value("in"), Value("array"))), + SharedConstant(Array(Value("1"), Value(42LL), Value(true)))})), + Returns(Value(true))); +} + +TEST_F(ArrayContainsAllTest, DoesNotContainAll) { + EXPECT_THAT(EvaluateExpr(*ArrayContainsAllExpr( + {SharedConstant(Array(Value("1"), Value(42LL), Value(true))), + SharedConstant(Array(Value("1"), Value(99LL)))})), + Returns(Value(false))); +} + +TEST_F(ArrayContainsAllTest, EquivalentNumerics) { + EXPECT_THAT( + EvaluateExpr(*ArrayContainsAllExpr( + {SharedConstant(Array(Value(42LL), Value(true), Value("additional"), + Value("values"), Value("in"), Value("array"))), + SharedConstant(Array(Value(42.0), Value(true)))})), + Returns(Value(true))); +} + +TEST_F(ArrayContainsAllTest, ArrayToSearchIsEmpty) { + EXPECT_THAT(EvaluateExpr(*ArrayContainsAllExpr( + {SharedConstant(Array()), + SharedConstant(Array(Value(42.0), Value(true)))})), + Returns(Value(false))); +} + +TEST_F(ArrayContainsAllTest, SearchValueIsEmpty) { + EXPECT_THAT(EvaluateExpr(*ArrayContainsAllExpr( + {SharedConstant(Array(Value(42.0), Value(true))), + SharedConstant(Array())})), + Returns(Value(true))); +} + +TEST_F(ArrayContainsAllTest, SearchValueIsNaN) { + // NaN comparison always returns false in Firestore + EXPECT_THAT( + EvaluateExpr(*ArrayContainsAllExpr( + {SharedConstant(Array(Value(std::numeric_limits::quiet_NaN()), + Value(42.0))), + SharedConstant( + Array(Value(std::numeric_limits::quiet_NaN())))})), + Returns(Value(false))); +} + +TEST_F(ArrayContainsAllTest, SearchValueHasDuplicates) { + EXPECT_THAT( + EvaluateExpr(*ArrayContainsAllExpr( + {SharedConstant(Array(Value(true), Value("hi"))), + SharedConstant(Array(Value(true), Value(true), Value(true)))})), + Returns(Value(true))); +} + +TEST_F(ArrayContainsAllTest, ArrayToSearchIsEmptySearchValueIsEmpty) { + EXPECT_THAT(EvaluateExpr(*ArrayContainsAllExpr( + {SharedConstant(Array()), SharedConstant(Array())})), + Returns(Value(true))); +} + +TEST_F(ArrayContainsAllTest, LargeNumberOfElements) { + // Construct the array to search expression + std::vector> + elements_to_search_vec; + elements_to_search_vec.reserve(500); + for (int i = 1; i <= 500; ++i) { + elements_to_search_vec.push_back(Value(static_cast(i))); + } + auto array_to_search_expr = + SharedConstant(model::ArrayValue(std::move(elements_to_search_vec))); + + // Construct the list of expressions to find + std::vector> + elements_to_find_exprs; + elements_to_find_exprs.reserve(500); + for (int i = 1; i <= 500; ++i) { + elements_to_find_exprs.push_back(Value(static_cast(i))); + } + auto elements_to_find_expr = + SharedConstant(model::ArrayValue(std::move(elements_to_search_vec))); + + // Pass the combined vector to the helper + EXPECT_THAT(EvaluateExpr(*ArrayContainsAllExpr( + {array_to_search_expr, elements_to_find_expr})), + Returns(Value(true))); +} + +// --- ArrayContainsAny Tests --- + +TEST_F(ArrayContainsAnyTest, ValueFoundInArray) { + auto array_to_search = + SharedConstant(Array(Value(42LL), Value("matang"), Value(true))); + EXPECT_THAT(EvaluateExpr(*ArrayContainsAnyExpr( + {array_to_search, + SharedConstant(Array(Value("matang"), Value(false)))})), + Returns(Value(true))); +} + +TEST_F(ArrayContainsAnyTest, EquivalentNumerics) { + auto array_to_search = + SharedConstant(Array(Value(42LL), Value("matang"), Value(true))); + EXPECT_THAT( + EvaluateExpr(*ArrayContainsAnyExpr( + {array_to_search, SharedConstant(Array(Value(42.0), Value(2LL)))})), + Returns(Value(true))); +} + +TEST_F(ArrayContainsAnyTest, ValuesNotFoundInArray) { + auto array_to_search = + SharedConstant(Array(Value(42LL), Value("matang"), Value(true))); + EXPECT_THAT(EvaluateExpr(*ArrayContainsAnyExpr( + {array_to_search, + SharedConstant(Array(Value(99LL), Value("false")))})), + Returns(Value(false))); +} + +TEST_F(ArrayContainsAnyTest, BothInputTypeIsArray) { + auto array_to_search = + SharedConstant(Array(Array(Value(1LL), Value(2LL), Value(3LL)), + Array(Value(4LL), Value(5LL), Value(6LL)), + Array(Value(7LL), Value(8LL), Value(9LL)))); + auto values_to_find = + SharedConstant(Array(Array(Value(1LL), Value(2LL), Value(3LL)), + Array(Value(4LL), Value(5LL), Value(6LL)))); + EXPECT_THAT( + EvaluateExpr(*ArrayContainsAnyExpr({array_to_search, values_to_find})), + Returns(Value(true))); +} + +TEST_F(ArrayContainsAnyTest, SearchIsNullReturnsNull) { + auto array_to_search = SharedConstant( + Array(Value(nullptr), Value(1LL), Value("matang"), Value(true))); + EXPECT_THAT(EvaluateExpr(*ArrayContainsAnyExpr( + {array_to_search, SharedConstant(Array(Value(nullptr)))})), + ReturnsNull()); +} + +TEST_F(ArrayContainsAnyTest, ArrayIsNotArrayTypeReturnsError) { + EXPECT_THAT(EvaluateExpr(*ArrayContainsAnyExpr( + {SharedConstant("matang"), + SharedConstant(Array(Value("matang"), Value(false)))})), + ReturnsError()); +} + +TEST_F(ArrayContainsAnyTest, SearchIsNotArrayTypeReturnsError) { + EXPECT_THAT(EvaluateExpr(*ArrayContainsAnyExpr( + {SharedConstant(Array(Value("matang"), Value(false))), + SharedConstant("matang")})), + ReturnsError()); +} + +TEST_F(ArrayContainsAnyTest, ArrayNotFoundReturnsError) { + EXPECT_THAT(EvaluateExpr(*ArrayContainsAnyExpr( + {std::make_shared("not-exist"), + SharedConstant(Array(Value("matang"), Value(false)))})), + ReturnsError()); +} + +TEST_F(ArrayContainsAnyTest, SearchNotFoundReturnsError) { + auto array_to_search = + SharedConstant(Array(Value(42LL), Value("matang"), Value(true))); + EXPECT_THAT( + EvaluateExpr(*ArrayContainsAnyExpr( + {array_to_search, std::make_shared("not-exist")})), + ReturnsError()); +} + +// --- ArrayContains Tests --- + +TEST_F(ArrayContainsTest, ValueFoundInArray) { + EXPECT_THAT(EvaluateExpr(*ArrayContainsExpr( + {SharedConstant(Array(Value("hello"), Value("world"))), + SharedConstant("hello")})), + Returns(Value(true))); +} + +TEST_F(ArrayContainsTest, ValueNotFoundInArray) { + auto array_to_search = + SharedConstant(Array(Value(42LL), Value("matang"), Value(true))); + EXPECT_THAT( + EvaluateExpr(*ArrayContainsExpr({array_to_search, SharedConstant(4LL)})), + Returns(Value(false))); +} + +// Note: `not` function is not directly available as an expression builder yet. +// TEST_F(ArrayContainsTest, NotArrayContainsFunctionValueNotFoundInArray) { ... +// } + +TEST_F(ArrayContainsTest, EquivalentNumerics) { + auto array_to_search = + SharedConstant(Array(Value(42LL), Value("matang"), Value(true))); + EXPECT_THAT( + EvaluateExpr(*ArrayContainsExpr({array_to_search, SharedConstant(42.0)})), + Returns(Value(true))); +} + +TEST_F(ArrayContainsTest, BothInputTypeIsArray) { + auto array_to_search = + SharedConstant(Array(Array(Value(1LL), Value(2LL), Value(3LL)), + Array(Value(4LL), Value(5LL), Value(6LL)), + Array(Value(7LL), Value(8LL), Value(9LL)))); + auto value_to_find = + SharedConstant(Array(Value(1LL), Value(2LL), Value(3LL))); + EXPECT_THAT( + EvaluateExpr(*ArrayContainsExpr({array_to_search, value_to_find})), + Returns(Value(true))); +} + +TEST_F(ArrayContainsTest, SearchValueIsNullReturnsNull) { + auto array_to_search = SharedConstant( + Array(Value(nullptr), Value(1LL), Value("matang"), Value(true))); + EXPECT_THAT(EvaluateExpr(*ArrayContainsExpr( + {array_to_search, SharedConstant(nullptr)})), + ReturnsNull()); // Null comparison returns Null +} + +TEST_F(ArrayContainsTest, SearchValueIsNullEmptyValuesArrayReturnsNull) { + EXPECT_THAT(EvaluateExpr(*ArrayContainsExpr( + {SharedConstant(Array()), SharedConstant(nullptr)})), + ReturnsNull()); // Null comparison returns Null +} + +TEST_F(ArrayContainsTest, SearchValueIsMap) { + auto array_expr = + SharedConstant(Array(Value(123LL), Map("foo", Value(123LL)), + Map("bar", Value(42LL)), Map("foo", Value(42LL)))); + auto map_expr = SharedConstant(Map("foo", Value(42LL))); + EXPECT_THAT(EvaluateExpr(*ArrayContainsExpr({array_expr, map_expr})), + Returns(Value(true))); +} + +TEST_F(ArrayContainsTest, SearchValueIsNaN) { + // NaN comparison always returns false + auto array_expr = SharedConstant( + Array(Value(std::numeric_limits::quiet_NaN()), Value("foo"))); + auto nan_expr = SharedConstant(std::numeric_limits::quiet_NaN()); + EXPECT_THAT(EvaluateExpr(*ArrayContainsExpr({array_expr, nan_expr})), + Returns(Value(false))); +} + +TEST_F(ArrayContainsTest, ArrayToSearchIsNotArrayTypeReturnsError) { + EXPECT_THAT(EvaluateExpr(*ArrayContainsExpr( + {SharedConstant("matang"), SharedConstant("values")})), + ReturnsError()); +} + +TEST_F(ArrayContainsTest, ArrayToSearchNotFoundReturnsError) { + EXPECT_THAT(EvaluateExpr( + *ArrayContainsExpr({std::make_shared("not-exist"), + SharedConstant("matang")})), + ReturnsError()); // Field not found results in Unset +} + +TEST_F(ArrayContainsTest, ArrayToSearchIsEmptyReturnsFalse) { + EXPECT_THAT(EvaluateExpr(*ArrayContainsExpr( + {SharedConstant(Array()), SharedConstant("matang")})), + Returns(Value(false))); +} + +TEST_F(ArrayContainsTest, SearchValueReferenceNotFoundReturnsError) { + auto array_to_search = + SharedConstant(Array(Value(42LL), Value("matang"), Value(true))); + EXPECT_THAT( + EvaluateExpr(*ArrayContainsExpr( + {array_to_search, std::make_shared("not-exist")})), + ReturnsError()); // Field not found results in Unset +} + +// --- ArrayLength Tests --- + +TEST_F(ArrayLengthTest, Length) { + EXPECT_THAT(EvaluateExpr(*ArrayLengthExpr({SharedConstant( + Array(Value("1"), Value(42LL), Value(true)))})), + Returns(Value(3LL))); +} + +TEST_F(ArrayLengthTest, EmptyArray) { + EXPECT_THAT(EvaluateExpr(*ArrayLengthExpr({SharedConstant(Array())})), + Returns(Value(0LL))); +} + +TEST_F(ArrayLengthTest, ArrayWithDuplicateElements) { + EXPECT_THAT(EvaluateExpr(*ArrayLengthExpr( + {SharedConstant(Array(Value(true), Value(true)))})), + Returns(Value(2LL))); +} + +TEST_F(ArrayLengthTest, NotArrayTypeReturnsError) { + // VectorValue not directly supported as FieldValue yet. + // Test with other non-array types. + EXPECT_THAT(EvaluateExpr(*ArrayLengthExpr({SharedConstant("notAnArray")})), + ReturnsError()); + EXPECT_THAT(EvaluateExpr(*ArrayLengthExpr({SharedConstant(123LL)})), + ReturnsError()); + EXPECT_THAT(EvaluateExpr(*ArrayLengthExpr({SharedConstant(true)})), + ReturnsError()); + EXPECT_THAT(EvaluateExpr(*ArrayLengthExpr({SharedConstant(Map())})), + ReturnsError()); +} + +} // namespace core +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/core/expressions/debug_test.cc b/Firestore/core/test/unit/core/expressions/debug_test.cc new file mode 100644 index 00000000000..9b6ed4df06a --- /dev/null +++ b/Firestore/core/test/unit/core/expressions/debug_test.cc @@ -0,0 +1,150 @@ +/* + * 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 +#include // Required for quiet_NaN() +#include +#include + +#include "Firestore/core/src/api/expressions.h" // For api::Expr, api::IsError +#include "Firestore/core/src/core/expressions_eval.h" +// #include "Firestore/core/src/model/field_value.h" // Not needed, +// True/FalseValue are in value_util.h +#include "Firestore/core/src/model/value_util.h" // For value constants like NullValue, TrueValue, FalseValue +#include "Firestore/core/test/unit/testutil/expression_test_util.h" // For test helpers +#include "Firestore/core/test/unit/testutil/testutil.h" // For test helpers like Value, Array, Map +#include "gmock/gmock.h" // For matchers like Returns +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace core { + +using api::Expr; +using testutil::Array; +using testutil::ArrayLengthExpr; +using testutil::ComparisonValueTestData; +using testutil::Constant; // Use testutil::Constant for consistency +using testutil::EvaluateExpr; +using testutil::ExistsExpr; +using testutil::Field; +using testutil::IsErrorExpr; +using testutil::Map; +using testutil::NotExpr; +using testutil::Returns; +using testutil::ReturnsError; +using testutil::ReturnsNull; +using testutil::ReturnsUnset; +using testutil::SharedConstant; +// Unset is represented by evaluating Field("non-existent-field") +using model::FalseValue; +using model::TrueValue; +using testutil::Value; + +// Fixture for Debug function tests +class DebugTest : public ::testing::Test {}; + +// --- Exists Tests --- + +TEST_F(DebugTest, AnythingButUnsetReturnsTrue) { + for (const auto& value_expr : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT(EvaluateExpr(*ExistsExpr(value_expr)), + Returns(testutil::Value(true))); + } +} + +TEST_F(DebugTest, NullReturnsTrue) { + EXPECT_THAT(EvaluateExpr(*ExistsExpr(SharedConstant(nullptr))), + Returns(testutil::Value(true))); +} + +TEST_F(DebugTest, ErrorReturnsError) { + // Create an expression that evaluates to error (e.g., array_length on + // non-array) + auto error_producing_expr = + testutil::ArrayLengthExpr(SharedConstant("notAnArray")); + EXPECT_THAT(EvaluateExpr(*ExistsExpr(error_producing_expr)), ReturnsError()); +} + +TEST_F(DebugTest, UnsetWithNotExistsReturnsTrue) { + auto unset_expr = std::make_shared("non-existent-field"); + auto exists_expr = ExistsExpr(unset_expr); + EXPECT_THAT(EvaluateExpr(*NotExpr(exists_expr)), Returns(Value(true))); +} + +TEST_F(DebugTest, UnsetReturnsFalse) { + auto unset_expr = std::make_shared("non-existent-field"); + EXPECT_THAT(EvaluateExpr(*ExistsExpr(unset_expr)), Returns(Value(false))); +} + +TEST_F(DebugTest, EmptyArrayReturnsTrue) { + EXPECT_THAT(EvaluateExpr(*ExistsExpr(SharedConstant(Array()))), + Returns(Value(true))); +} + +TEST_F(DebugTest, EmptyMapReturnsTrue) { + EXPECT_THAT(EvaluateExpr(*ExistsExpr(SharedConstant(Map()))), + Returns(Value(true))); +} + +// --- IsError Tests --- + +TEST_F(DebugTest, IsErrorErrorReturnsTrue) { + // Use ArrayLengthExpr on a non-array to generate an error + auto error_producing_expr = ArrayLengthExpr(SharedConstant("notAnArray")); + EXPECT_THAT(EvaluateExpr(*IsErrorExpr(error_producing_expr)), + Returns(Value(true))); +} + +TEST_F(DebugTest, IsErrorFieldMissingReturnsFalse) { + // Evaluate with context that does *not* contain 'target' + auto field_expr = std::make_shared("target"); + EXPECT_THAT(EvaluateExpr(*IsErrorExpr(field_expr)), Returns(Value(false))); +} + +TEST_F(DebugTest, IsErrorNonErrorReturnsFalse) { + EXPECT_THAT(EvaluateExpr(*IsErrorExpr(SharedConstant(42LL))), + Returns(Value(false))); +} + +TEST_F(DebugTest, IsErrorExplicitNullReturnsFalse) { + EXPECT_THAT(EvaluateExpr(*IsErrorExpr(SharedConstant(nullptr))), + Returns(Value(false))); +} + +TEST_F(DebugTest, IsErrorUnsetReturnsFalse) { + // Evaluating a non-existent field results in Unset, which is not an error + auto unset_expr = std::make_shared("non-existent-field"); + EXPECT_THAT(EvaluateExpr(*IsErrorExpr(unset_expr)), + Returns(Value(false))); // Wrap FalseValue +} + +TEST_F(DebugTest, IsErrorAnythingButErrorReturnsFalse) { + for (const auto& value_expr : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT(EvaluateExpr(*IsErrorExpr(value_expr)), Returns(Value(false))); + } + // Also test explicit null and integer 0 which might not be in the main list + EXPECT_THAT(EvaluateExpr(*IsErrorExpr(SharedConstant(nullptr))), + Returns(Value(false))); + EXPECT_THAT(EvaluateExpr(*IsErrorExpr(SharedConstant(int64_t{0}))), + Returns(Value(false))); +} + +} // namespace core +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/core/expressions/field_test.cc b/Firestore/core/test/unit/core/expressions/field_test.cc new file mode 100644 index 00000000000..6d134be7b5a --- /dev/null +++ b/Firestore/core/test/unit/core/expressions/field_test.cc @@ -0,0 +1,57 @@ +/* + * 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 + +#include "Firestore/core/src/api/expressions.h" // For api::Expr +#include "Firestore/core/src/core/expressions_eval.h" +#include "Firestore/core/src/model/value_util.h" // For value constants +#include "Firestore/core/test/unit/testutil/expression_test_util.h" // For test helpers +#include "Firestore/core/test/unit/testutil/testutil.h" // For test helpers like Value, Map, Doc +#include "gmock/gmock.h" // For matchers like Returns +#include "gtest/gtest.h" + +namespace firebase { +namespace firestore { +namespace core { + +using testutil::Doc; +using testutil::EvaluateExpr; +using testutil::Map; +using testutil::Returns; +using testutil::ReturnsUnset; +using testutil::Value; + +// Fixture for Field expression tests +class FieldTest : public ::testing::Test {}; + +// --- Field Tests --- + +TEST_F(FieldTest, CanGetField) { + // Create a document with the field "exists" set to true. + auto doc_with_field = Doc("coll/doc1", 1, Map("exists", Value(true))); + auto field_expr = std::make_shared("exists"); + EXPECT_THAT(EvaluateExpr(*field_expr, doc_with_field), Returns(Value(true))); +} + +TEST_F(FieldTest, ReturnsUnsetIfNotFound) { + auto field_expr = std::make_shared("not-exists"); + EXPECT_THAT(EvaluateExpr(*field_expr), ReturnsUnset()); +} + +} // namespace core +} // namespace firestore +} // namespace firebase diff --git a/Firestore/core/test/unit/core/expressions/logical_test.cc b/Firestore/core/test/unit/core/expressions/logical_test.cc new file mode 100644 index 00000000000..81633e2c106 --- /dev/null +++ b/Firestore/core/test/unit/core/expressions/logical_test.cc @@ -0,0 +1,1155 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include "Firestore/core/src/api/expressions.h" +#include "Firestore/core/src/core/expressions_eval.h" +#include "Firestore/core/src/model/field_path.h" +#include "Firestore/core/src/model/value_util.h" // For TrueValue, FalseValue, NullValue +#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::FieldPath; +// Removed: using model::FieldValue; // Use model::FieldValue explicitly +using testing::_; +using testutil::AddExpr; +using testutil::AndExpr; +using testutil::Array; +using testutil::ComparisonValueTestData; +using testutil::CondExpr; +using testutil::Doc; +using testutil::EqAnyExpr; +using testutil::EvaluateExpr; +using testutil::IsNanExpr; +using testutil::IsNotNanExpr; +using testutil::IsNotNullExpr; +using testutil::IsNullExpr; +using testutil::LogicalMaxExpr; +using testutil::LogicalMinExpr; +using testutil::Map; +using testutil::NotExpr; +using testutil::OrExpr; +using testutil::Returns; +using testutil::ReturnsError; // Using ReturnsUnset as equivalent for now +// Removed: using testutil::ReturnsFalse; +// Removed: using testutil::ReturnsMin; // Use ReturnsNull for null comparisons +using testutil::ReturnsNull; +// Removed: using testutil::ReturnsTrue; +using testutil::ReturnsUnset; +using testutil::SharedConstant; +using testutil::Value; +using testutil::XorExpr; + +// Helper function to create a Field expression using the specified path. +// Follows the instruction to use std::make_shared directly. +std::shared_ptr Field(const std::string& path) { + return std::make_shared(FieldPath::FromDotSeparatedString(path)); +} + +// Removed redundant Constant helper + +// Predefined constants for convenience (defined directly) +const auto TrueExpr = testutil::SharedConstant(model::TrueValue()); +const auto FalseExpr = testutil::SharedConstant(model::FalseValue()); +const auto NullExpr = testutil::SharedConstant(model::NullValue()); +const auto NanExpr = + testutil::SharedConstant(Value(std::numeric_limits::quiet_NaN())); + +// Placeholder for an expression that results in an error/unset value during +// evaluation. Using a non-existent field path often achieves this with default +// test documents. +std::shared_ptr ErrorExpr() { + // Using a field path known to cause issues if the input doc isn't structured + // correctly, or simply a non-existent field. + return Field("error.field"); +} + +// Base fixture for logical expression tests +class LogicalExpressionsTest : public ::testing::Test { + protected: + // Add common setup/data if needed later + // Example document for field path evaluation: + model::MutableDocument test_doc_ = + Doc("coll/doc", 1, Map("nanValue", Value(NAN), "field", Value("value"))); + model::MutableDocument error_doc_ = + Doc("coll/doc", 1, Map("error", 123)); // Doc where error.field fails +}; + +// --- And (&&) Tests --- +class AndFunctionTest : public LogicalExpressionsTest {}; + +// 2 Operands +TEST_F(AndFunctionTest, FalseFalseIsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::AndExpr({FalseExpr, FalseExpr})), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, FalseErrorIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({FalseExpr, ErrorExpr()}), error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, FalseTrueIsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::AndExpr({FalseExpr, TrueExpr})), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, ErrorFalseIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({ErrorExpr(), FalseExpr}), error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, ErrorErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({ErrorExpr(), ErrorExpr()}), error_doc_), + ReturnsError()); +} +TEST_F(AndFunctionTest, ErrorTrueIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({ErrorExpr(), TrueExpr}), error_doc_), + ReturnsError()); +} +TEST_F(AndFunctionTest, TrueFalseIsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::AndExpr({TrueExpr, FalseExpr})), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, TrueErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({TrueExpr, ErrorExpr()}), error_doc_), + ReturnsError()); +} +TEST_F(AndFunctionTest, TrueTrueIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::AndExpr({TrueExpr, TrueExpr})), + Returns(Value(true))); +} + +// 3 Operands +TEST_F(AndFunctionTest, FalseFalseFalseIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({FalseExpr, FalseExpr, FalseExpr})), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, FalseFalseErrorIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({FalseExpr, FalseExpr, ErrorExpr()}), + error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, FalseFalseTrueIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({FalseExpr, FalseExpr, TrueExpr})), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, FalseErrorFalseIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({FalseExpr, ErrorExpr(), FalseExpr}), + error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, FalseErrorErrorIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({FalseExpr, ErrorExpr(), ErrorExpr()}), + error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, FalseErrorTrueIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({FalseExpr, ErrorExpr(), TrueExpr}), + error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, FalseTrueFalseIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({FalseExpr, TrueExpr, FalseExpr})), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, FalseTrueErrorIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({FalseExpr, TrueExpr, ErrorExpr()}), + error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, FalseTrueTrueIsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::AndExpr({FalseExpr, TrueExpr, TrueExpr})), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, ErrorFalseFalseIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({ErrorExpr(), FalseExpr, FalseExpr}), + error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, ErrorFalseErrorIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({ErrorExpr(), FalseExpr, ErrorExpr()}), + error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, ErrorFalseTrueIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({ErrorExpr(), FalseExpr, TrueExpr}), + error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, ErrorErrorFalseIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({ErrorExpr(), ErrorExpr(), FalseExpr}), + error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, ErrorErrorErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({ErrorExpr(), ErrorExpr(), ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(AndFunctionTest, ErrorErrorTrueIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({ErrorExpr(), ErrorExpr(), TrueExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(AndFunctionTest, ErrorTrueFalseIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({ErrorExpr(), TrueExpr, FalseExpr}), + error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, ErrorTrueErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({ErrorExpr(), TrueExpr, ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(AndFunctionTest, ErrorTrueTrueIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({ErrorExpr(), TrueExpr, TrueExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(AndFunctionTest, TrueFalseFalseIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({TrueExpr, FalseExpr, FalseExpr})), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, TrueFalseErrorIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({TrueExpr, FalseExpr, ErrorExpr()}), + error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, TrueFalseTrueIsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::AndExpr({TrueExpr, FalseExpr, TrueExpr})), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, TrueErrorFalseIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({TrueExpr, ErrorExpr(), FalseExpr}), + error_doc_), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, TrueErrorErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({TrueExpr, ErrorExpr(), ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(AndFunctionTest, TrueErrorTrueIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({TrueExpr, ErrorExpr(), TrueExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(AndFunctionTest, TrueTrueFalseIsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::AndExpr({TrueExpr, TrueExpr, FalseExpr})), + Returns(Value(false))); +} +TEST_F(AndFunctionTest, TrueTrueErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::AndExpr({TrueExpr, TrueExpr, ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(AndFunctionTest, TrueTrueTrueIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::AndExpr({TrueExpr, TrueExpr, TrueExpr})), + Returns(Value(true))); +} + +// Nested +TEST_F(AndFunctionTest, NestedAnd) { + auto child = testutil::AndExpr({TrueExpr, FalseExpr}); + auto f = testutil::AndExpr({child, TrueExpr}); + EXPECT_THAT(EvaluateExpr(*f), Returns(Value(false))); +} + +// Multiple Arguments (already covered by 3-operand tests) +TEST_F(AndFunctionTest, MultipleArguments) { + EXPECT_THAT(EvaluateExpr(*testutil::AndExpr({TrueExpr, TrueExpr, TrueExpr})), + Returns(Value(true))); +} + +// --- Cond (? :) Tests --- +class CondFunctionTest : public LogicalExpressionsTest {}; + +TEST_F(CondFunctionTest, TrueConditionReturnsTrueCase) { + auto expr = testutil::CondExpr(TrueExpr, SharedConstant(Value("true case")), + ErrorExpr()); + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value("true case"))); +} + +TEST_F(CondFunctionTest, FalseConditionReturnsFalseCase) { + auto expr = testutil::CondExpr(FalseExpr, ErrorExpr(), + SharedConstant(Value("false case"))); + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value("false case"))); +} + +TEST_F(CondFunctionTest, ErrorConditionReturnsError) { + auto expr = testutil::CondExpr(ErrorExpr(), ErrorExpr(), + SharedConstant(Value("false"))); + // If condition is error, the whole expression is error + EXPECT_THAT(EvaluateExpr(*expr, error_doc_), ReturnsError()); +} + +// --- EqAny Tests --- +class EqAnyFunctionTest : public LogicalExpressionsTest {}; + +TEST_F(EqAnyFunctionTest, ValueFoundInArray) { + auto expr = testutil::EqAnyExpr( + SharedConstant(Value("hello")), + SharedConstant(Array(Value("hello"), Value("world")))); + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value(true))); +} + +TEST_F(EqAnyFunctionTest, ValueNotFoundInArray) { + auto expr = testutil::EqAnyExpr( + SharedConstant(Value(4LL)), + SharedConstant(Array(Value(42LL), Value("matang"), Value(true)))); + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value(false))); +} + +TEST_F(EqAnyFunctionTest, NotEqAnyFunctionValueNotFoundInArray) { + auto child = testutil::NotEqAnyExpr( + SharedConstant(Value(4LL)), + SharedConstant(Array(Value(42LL), Value("matang"), Value(true)))); + EXPECT_THAT(EvaluateExpr(*child), Returns(Value(true))); +} + +TEST_F(EqAnyFunctionTest, EquivalentNumerics) { + EXPECT_THAT( + EvaluateExpr(*testutil::EqAnyExpr( + SharedConstant(Value(42LL)), + SharedConstant(Array(Value(42.0), Value("matang"), Value(true))))), + Returns(Value(true))); + EXPECT_THAT( + EvaluateExpr(*testutil::EqAnyExpr( + SharedConstant(Value(42.0)), + SharedConstant(Array(Value(42LL), Value("matang"), Value(true))))), + Returns(Value(true))); +} + +TEST_F(EqAnyFunctionTest, BothInputTypeIsArray) { + auto search_array = SharedConstant(Array(Value(1LL), Value(2LL), Value(3LL))); + auto values_array = + SharedConstant(Array(Array(Value(1LL), Value(2LL), Value(3LL)), + Array(Value(4LL), Value(5LL), Value(6LL)), + Array(Value(7LL), Value(8LL), Value(9LL)))); + EXPECT_THAT(EvaluateExpr(*testutil::EqAnyExpr(search_array, values_array)), + Returns(Value(true))); +} + +TEST_F(EqAnyFunctionTest, ArrayNotFoundReturnsError) { + // If any element in the values array evaluates to error/unset, the result is + // error/unset + auto expr = testutil::EqAnyExpr(SharedConstant(Value("matang")), + Field("non-existent-field")); + EXPECT_THAT(EvaluateExpr(*expr), ReturnsError()); +} + +TEST_F(EqAnyFunctionTest, ArrayIsEmptyReturnsFalse) { + auto expr = + testutil::EqAnyExpr(SharedConstant(Value(42LL)), SharedConstant(Array())); + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value(false))); +} + +TEST_F(EqAnyFunctionTest, SearchReferenceNotFoundReturnsError) { + auto expr = testutil::EqAnyExpr( + Field("non-existent-field"), + SharedConstant(Array(Value(42LL), Value("matang"), Value(true)))); + EXPECT_THAT(EvaluateExpr(*expr), ReturnsError()); +} + +TEST_F(EqAnyFunctionTest, SearchIsNull) { + // Null comparison returns Null + auto expr = testutil::EqAnyExpr( + NullExpr, SharedConstant(Array(Value(nullptr), Value(1LL), + Value("matang"), Value(true)))); + EXPECT_THAT(EvaluateExpr(*expr), ReturnsNull()); +} + +TEST_F(EqAnyFunctionTest, SearchIsNullEmptyValuesArrayReturnsNull) { + // Null comparison returns Null + auto expr = testutil::EqAnyExpr(NullExpr, SharedConstant(Array())); + EXPECT_THAT(EvaluateExpr(*expr), ReturnsNull()); +} + +TEST_F(EqAnyFunctionTest, SearchIsNaN) { + // NaN comparison always returns false + auto expr = testutil::EqAnyExpr( + NanExpr, + SharedConstant(Array(Value(std::numeric_limits::quiet_NaN()), + Value(42LL), Value(3.14)))); + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value(false))); +} + +TEST_F(EqAnyFunctionTest, SearchIsEmptyArrayIsEmpty) { + auto expr = + testutil::EqAnyExpr(SharedConstant(Array()), SharedConstant(Array())); + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value(false))); +} + +TEST_F(EqAnyFunctionTest, SearchIsEmptyArrayContainsEmptyArrayReturnsTrue) { + auto expr = testutil::EqAnyExpr(SharedConstant(Array()), + SharedConstant(Array(Array()))); + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value(true))); +} + +TEST_F(EqAnyFunctionTest, SearchIsMap) { + auto search_map = SharedConstant(Map("foo", Value(42LL))); + auto values_array = + SharedConstant(Array(Array(Value(123LL), Map("foo", Value(123LL))), + Map("bar", Value(42LL)), Map("foo", Value(42LL)))); + EXPECT_THAT(EvaluateExpr(*testutil::EqAnyExpr(search_map, values_array)), + Returns(Value(true))); +} + +// --- IsNan / IsNotNan Tests --- +class IsNanFunctionTest : public LogicalExpressionsTest {}; + +TEST_F(IsNanFunctionTest, NanReturnsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::IsNanExpr(NanExpr)), + Returns(Value(true))); + EXPECT_THAT(EvaluateExpr(*testutil::IsNanExpr(Field("nanValue")), test_doc_), + Returns(Value(true))); +} + +TEST_F(IsNanFunctionTest, NotNanReturnsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::IsNanExpr(SharedConstant(Value(42.0)))), + Returns(Value(false))); + EXPECT_THAT(EvaluateExpr(*testutil::IsNanExpr(SharedConstant(Value(42LL)))), + Returns(Value(false))); +} + +TEST_F(IsNanFunctionTest, IsNotNan) { + EXPECT_THAT( + EvaluateExpr(*testutil::IsNotNanExpr(SharedConstant(Value(42.0)))), + Returns(Value(true))); + EXPECT_THAT( + EvaluateExpr(*testutil::IsNotNanExpr(SharedConstant(Value(42LL)))), + Returns(Value(true))); + EXPECT_THAT(EvaluateExpr(*testutil::IsNotNanExpr(NanExpr)), + Returns(Value(false))); + EXPECT_THAT( + EvaluateExpr(*testutil::IsNotNanExpr(Field("nanValue")), test_doc_), + Returns(Value(false))); +} + +TEST_F(IsNanFunctionTest, OtherNanRepresentationsReturnsTrue) { + // Note: C++ standard doesn't guarantee specific results for Inf - Inf, etc. + // Relying on NaN constant and NaN propagation. + EXPECT_THAT(EvaluateExpr(*testutil::IsNanExpr(SharedConstant(Value(NAN)))), + Returns(Value(true))); + + // Test NaN propagation (e.g., NaN + 1 -> NaN) + auto nan_plus_one = testutil::AddExpr({NanExpr, SharedConstant(Value(1LL))}); + EXPECT_THAT(EvaluateExpr(*testutil::IsNanExpr(nan_plus_one)), + Returns(Value(true))); + + // Test Inf - Inf (may not produce NaN reliably across platforms/compilers) + // auto inf_minus_inf = testutil::AddExpr({SharedConstant(Value(INFINITY)), + // SharedConstant(Value(-INFINITY))}); + // EXPECT_THAT(EvaluateExpr(*testutil::IsNanExpr(inf_minus_inf)), + // Returns(Value(true))); // This might fail +} + +TEST_F(IsNanFunctionTest, NonNumericReturnsError) { + EXPECT_THAT(EvaluateExpr(*testutil::IsNanExpr(SharedConstant(Value(true)))), + ReturnsError()); + EXPECT_THAT(EvaluateExpr(*testutil::IsNanExpr(SharedConstant(Value("abc")))), + ReturnsError()); + EXPECT_THAT(EvaluateExpr(*testutil::IsNanExpr(NullExpr)), ReturnsNull()); + EXPECT_THAT(EvaluateExpr(*testutil::IsNanExpr(SharedConstant(Array()))), + ReturnsError()); + EXPECT_THAT(EvaluateExpr(*testutil::IsNanExpr(SharedConstant(Map()))), + ReturnsError()); +} + +// --- LogicalMaximum Tests --- +class LogicalMaximumFunctionTest : public LogicalExpressionsTest {}; + +TEST_F(LogicalMaximumFunctionTest, NumericType) { + auto expr = testutil::LogicalMaxExpr( + {SharedConstant(Value(1LL)), + testutil::LogicalMaxExpr( + {SharedConstant(Value(2.0)), SharedConstant(Value(3LL))})}); + EXPECT_THAT(EvaluateExpr(*expr), + Returns(Value(3LL))); // Max(1, Max(2.0, 3)) -> 3 +} + +TEST_F(LogicalMaximumFunctionTest, StringType) { + auto expr = testutil::LogicalMaxExpr( + {testutil::LogicalMaxExpr( + {SharedConstant(Value("a")), SharedConstant(Value("b"))}), + SharedConstant(Value("c"))}); + EXPECT_THAT(EvaluateExpr(*expr), + Returns(Value("c"))); // Max(Max("a", "b"), "c") -> "c" +} + +TEST_F(LogicalMaximumFunctionTest, MixedType) { + // Type order: Null < Bool < Number < Timestamp < String < Blob < Ref < + // GeoPoint < Array < Map + auto expr = testutil::LogicalMaxExpr( + {SharedConstant(Value(1LL)), + testutil::LogicalMaxExpr( + {SharedConstant(Value("1")), SharedConstant(Value(0LL))})}); + EXPECT_THAT( + EvaluateExpr(*expr), + Returns(Value("1"))); // Max(1, Max("1", 0)) -> "1" (String > Number) +} + +TEST_F(LogicalMaximumFunctionTest, OnlyNullAndErrorReturnsNull) { + auto expr = testutil::LogicalMaxExpr({NullExpr, ErrorExpr()}); + EXPECT_THAT(EvaluateExpr(*expr, error_doc_), ReturnsNull()); +} + +TEST_F(LogicalMaximumFunctionTest, NanAndNumbers) { + // NaN is handled specially; it's skipped unless it's the only non-null/error + // value. + auto expr = testutil::LogicalMaxExpr({NanExpr, SharedConstant(Value(0LL))}); + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value(0LL))); // Max(NaN, 0) -> 0 + auto expr2 = testutil::LogicalMaxExpr({SharedConstant(Value(0LL)), NanExpr}); + EXPECT_THAT(EvaluateExpr(*expr2), Returns(Value(0LL))); // Max(0, NaN) -> 0 + auto expr3 = testutil::LogicalMaxExpr({NanExpr, NullExpr, ErrorExpr()}); + EXPECT_THAT(EvaluateExpr(*expr3, error_doc_), + Returns(Value(NAN))); // Max(NaN, Null, Error) -> NaN + auto expr4 = testutil::LogicalMaxExpr({NanExpr, ErrorExpr()}); + EXPECT_THAT(EvaluateExpr(*expr4, error_doc_), + Returns(Value(NAN))); // Max(NaN, Error) -> NaN +} + +TEST_F(LogicalMaximumFunctionTest, ErrorInputSkip) { + auto expr = + testutil::LogicalMaxExpr({ErrorExpr(), SharedConstant(Value(1LL))}); + EXPECT_THAT(EvaluateExpr(*expr, error_doc_), Returns(Value(1LL))); +} + +TEST_F(LogicalMaximumFunctionTest, NullInputSkip) { + auto expr = testutil::LogicalMaxExpr({NullExpr, SharedConstant(Value(1LL))}); + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value(1LL))); +} + +TEST_F(LogicalMaximumFunctionTest, EquivalentNumerics) { + auto expr = testutil::LogicalMaxExpr( + {SharedConstant(Value(1LL)), SharedConstant(Value(1.0))}); + // Max(1, 1.0) -> 1 (or 1.0, they are equivalent, result depends on internal + // order) Let's check if it's equivalent to 1LL + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value(1LL))); +} + +// --- LogicalMinimum Tests --- +class LogicalMinimumFunctionTest : public LogicalExpressionsTest {}; + +TEST_F(LogicalMinimumFunctionTest, NumericType) { + auto expr = testutil::LogicalMinExpr( + {SharedConstant(Value(1LL)), + testutil::LogicalMinExpr( + {SharedConstant(Value(2.0)), SharedConstant(Value(3LL))})}); + EXPECT_THAT(EvaluateExpr(*expr), + Returns(Value(1LL))); // Min(1, Min(2.0, 3)) -> 1 +} + +TEST_F(LogicalMinimumFunctionTest, StringType) { + auto expr = testutil::LogicalMinExpr( + {testutil::LogicalMinExpr( + {SharedConstant(Value("a")), SharedConstant(Value("b"))}), + SharedConstant(Value("c"))}); + EXPECT_THAT(EvaluateExpr(*expr), + Returns(Value("a"))); // Min(Min("a", "b"), "c") -> "a" +} + +TEST_F(LogicalMinimumFunctionTest, MixedType) { + // Type order: Null < Bool < Number < Timestamp < String < Blob < Ref < + // GeoPoint < Array < Map + auto expr = testutil::LogicalMinExpr( + {SharedConstant(Value(1LL)), + testutil::LogicalMinExpr( + {SharedConstant(Value("1")), SharedConstant(Value(0LL))})}); + EXPECT_THAT( + EvaluateExpr(*expr), + Returns(Value(0LL))); // Min(1, Min("1", 0)) -> 0 (Number < String) +} + +TEST_F(LogicalMinimumFunctionTest, OnlyNullAndErrorReturnsNull) { + auto expr = testutil::LogicalMinExpr({NullExpr, ErrorExpr()}); + EXPECT_THAT(EvaluateExpr(*expr, error_doc_), ReturnsNull()); +} + +TEST_F(LogicalMinimumFunctionTest, NanAndNumbers) { + // NaN is handled specially; it's considered the minimum unless skipped. + auto expr = testutil::LogicalMinExpr({NanExpr, SharedConstant(Value(0LL))}); + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value(NAN))); // Min(NaN, 0) -> NaN + auto expr2 = testutil::LogicalMinExpr({SharedConstant(Value(0LL)), NanExpr}); + EXPECT_THAT(EvaluateExpr(*expr2), Returns(Value(NAN))); // Min(0, NaN) -> NaN + auto expr3 = testutil::LogicalMinExpr({NanExpr, NullExpr, ErrorExpr()}); + EXPECT_THAT(EvaluateExpr(*expr3, error_doc_), + Returns(Value(NAN))); // Min(NaN, Null, Error) -> NaN + auto expr4 = testutil::LogicalMinExpr({NanExpr, ErrorExpr()}); + EXPECT_THAT(EvaluateExpr(*expr4, error_doc_), + Returns(Value(NAN))); // Min(NaN, Error) -> NaN +} + +TEST_F(LogicalMinimumFunctionTest, ErrorInputSkip) { + auto expr = + testutil::LogicalMinExpr({ErrorExpr(), SharedConstant(Value(1LL))}); + EXPECT_THAT(EvaluateExpr(*expr, error_doc_), Returns(Value(1LL))); +} + +TEST_F(LogicalMinimumFunctionTest, NullInputSkip) { + auto expr = testutil::LogicalMinExpr({NullExpr, SharedConstant(Value(1LL))}); + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value(1LL))); +} + +TEST_F(LogicalMinimumFunctionTest, EquivalentNumerics) { + auto expr = testutil::LogicalMinExpr( + {SharedConstant(Value(1LL)), SharedConstant(Value(1.0))}); + // Min(1, 1.0) -> 1 (or 1.0, they are equivalent) + EXPECT_THAT(EvaluateExpr(*expr), Returns(Value(1LL))); +} + +// --- Not (!) Tests --- +class NotFunctionTest : public LogicalExpressionsTest {}; + +TEST_F(NotFunctionTest, TrueToFalse) { + // Using EqExpr from comparison_test helpers for simplicity + auto true_cond = testutil::EqExpr( + {SharedConstant(Value(1LL)), SharedConstant(Value(1LL))}); + EXPECT_THAT(EvaluateExpr(*testutil::NotExpr(true_cond)), + Returns(Value(false))); +} + +TEST_F(NotFunctionTest, FalseToTrue) { + // Using NeqExpr from comparison_test helpers for simplicity + auto false_cond = testutil::NeqExpr( + {SharedConstant(Value(1LL)), SharedConstant(Value(1LL))}); + EXPECT_THAT(EvaluateExpr(*testutil::NotExpr(false_cond)), + Returns(Value(true))); +} + +TEST_F(NotFunctionTest, NotErrorIsError) { + EXPECT_THAT(EvaluateExpr(*testutil::NotExpr(ErrorExpr()), error_doc_), + ReturnsError()); +} + +// --- Or (||) Tests --- +class OrFunctionTest : public LogicalExpressionsTest {}; + +// 2 Operands +TEST_F(OrFunctionTest, FalseFalseIsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({FalseExpr, FalseExpr})), + Returns(Value(false))); +} +TEST_F(OrFunctionTest, FalseErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({FalseExpr, ErrorExpr()}), error_doc_), + ReturnsError()); +} +TEST_F(OrFunctionTest, FalseTrueIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({FalseExpr, TrueExpr})), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, ErrorFalseIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({ErrorExpr(), FalseExpr}), error_doc_), + ReturnsError()); +} +TEST_F(OrFunctionTest, ErrorErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({ErrorExpr(), ErrorExpr()}), error_doc_), + ReturnsError()); +} +TEST_F(OrFunctionTest, ErrorTrueIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({ErrorExpr(), TrueExpr}), error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, TrueFalseIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({TrueExpr, FalseExpr})), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, TrueErrorIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({TrueExpr, ErrorExpr()}), error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, TrueTrueIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({TrueExpr, TrueExpr})), + Returns(Value(true))); +} + +// 3 Operands +TEST_F(OrFunctionTest, FalseFalseFalseIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({FalseExpr, FalseExpr, FalseExpr})), + Returns(Value(false))); +} +TEST_F(OrFunctionTest, FalseFalseErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({FalseExpr, FalseExpr, ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(OrFunctionTest, FalseFalseTrueIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({FalseExpr, FalseExpr, TrueExpr})), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, FalseErrorFalseIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({FalseExpr, ErrorExpr(), FalseExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(OrFunctionTest, FalseErrorErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({FalseExpr, ErrorExpr(), ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(OrFunctionTest, FalseErrorTrueIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({FalseExpr, ErrorExpr(), TrueExpr}), + error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, FalseTrueFalseIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({FalseExpr, TrueExpr, FalseExpr})), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, FalseTrueErrorIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({FalseExpr, TrueExpr, ErrorExpr()}), + error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, FalseTrueTrueIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({FalseExpr, TrueExpr, TrueExpr})), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, ErrorFalseFalseIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({ErrorExpr(), FalseExpr, FalseExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(OrFunctionTest, ErrorFalseErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({ErrorExpr(), FalseExpr, ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(OrFunctionTest, ErrorFalseTrueIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({ErrorExpr(), FalseExpr, TrueExpr}), + error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, ErrorErrorFalseIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({ErrorExpr(), ErrorExpr(), FalseExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(OrFunctionTest, ErrorErrorErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({ErrorExpr(), ErrorExpr(), ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(OrFunctionTest, ErrorErrorTrueIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({ErrorExpr(), ErrorExpr(), TrueExpr}), + error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, ErrorTrueFalseIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({ErrorExpr(), TrueExpr, FalseExpr}), + error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, ErrorTrueErrorIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({ErrorExpr(), TrueExpr, ErrorExpr()}), + error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, ErrorTrueTrueIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({ErrorExpr(), TrueExpr, TrueExpr}), + error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, TrueFalseFalseIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({TrueExpr, FalseExpr, FalseExpr})), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, TrueFalseErrorIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({TrueExpr, FalseExpr, ErrorExpr()}), + error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, TrueFalseTrueIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({TrueExpr, FalseExpr, TrueExpr})), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, TrueErrorFalseIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({TrueExpr, ErrorExpr(), FalseExpr}), + error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, TrueErrorErrorIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::OrExpr({TrueExpr, ErrorExpr(), ErrorExpr()}), + error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, TrueErrorTrueIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({TrueExpr, ErrorExpr(), TrueExpr}), + error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, TrueTrueFalseIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({TrueExpr, TrueExpr, FalseExpr})), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, TrueTrueErrorIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({TrueExpr, TrueExpr, ErrorExpr()}), + error_doc_), + Returns(Value(true))); +} +TEST_F(OrFunctionTest, TrueTrueTrueIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({TrueExpr, TrueExpr, TrueExpr})), + Returns(Value(true))); +} + +// Nested +TEST_F(OrFunctionTest, NestedOr) { + auto child = testutil::OrExpr({TrueExpr, FalseExpr}); + auto f = testutil::OrExpr({child, FalseExpr}); + EXPECT_THAT(EvaluateExpr(*f), Returns(Value(true))); +} + +// Multiple Arguments (already covered by 3-operand tests) +TEST_F(OrFunctionTest, MultipleArguments) { + EXPECT_THAT(EvaluateExpr(*testutil::OrExpr({TrueExpr, FalseExpr, TrueExpr})), + Returns(Value(true))); +} + +// --- Xor Tests --- +class XorFunctionTest : public LogicalExpressionsTest {}; + +// 2 Operands +TEST_F(XorFunctionTest, FalseFalseIsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::XorExpr({FalseExpr, FalseExpr})), + Returns(Value(false))); +} +TEST_F(XorFunctionTest, FalseErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({FalseExpr, ErrorExpr()}), error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, FalseTrueIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::XorExpr({FalseExpr, TrueExpr})), + Returns(Value(true))); +} +TEST_F(XorFunctionTest, ErrorFalseIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({ErrorExpr(), FalseExpr}), error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, ErrorErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({ErrorExpr(), ErrorExpr()}), error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, ErrorTrueIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({ErrorExpr(), TrueExpr}), error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, TrueFalseIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::XorExpr({TrueExpr, FalseExpr})), + Returns(Value(true))); +} +TEST_F(XorFunctionTest, TrueErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({TrueExpr, ErrorExpr()}), error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, TrueTrueIsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::XorExpr({TrueExpr, TrueExpr})), + Returns(Value(false))); +} + +// 3 Operands (XOR is true if an odd number of inputs are true) +TEST_F(XorFunctionTest, FalseFalseFalseIsFalse) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({FalseExpr, FalseExpr, FalseExpr})), + Returns(Value(false))); // 0 true -> false +} +TEST_F(XorFunctionTest, FalseFalseErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({FalseExpr, FalseExpr, ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, FalseFalseTrueIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({FalseExpr, FalseExpr, TrueExpr})), + Returns(Value(true))); // 1 true -> true +} +TEST_F(XorFunctionTest, FalseErrorFalseIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({FalseExpr, ErrorExpr(), FalseExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, FalseErrorErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({FalseExpr, ErrorExpr(), ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, FalseErrorTrueIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({FalseExpr, ErrorExpr(), TrueExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, FalseTrueFalseIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({FalseExpr, TrueExpr, FalseExpr})), + Returns(Value(true))); // 1 true -> true +} +TEST_F(XorFunctionTest, FalseTrueErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({FalseExpr, TrueExpr, ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, FalseTrueTrueIsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::XorExpr({FalseExpr, TrueExpr, TrueExpr})), + Returns(Value(false))); // 2 true -> false +} +TEST_F(XorFunctionTest, ErrorFalseFalseIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({ErrorExpr(), FalseExpr, FalseExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, ErrorFalseErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({ErrorExpr(), FalseExpr, ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, ErrorFalseTrueIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({ErrorExpr(), FalseExpr, TrueExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, ErrorErrorFalseIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({ErrorExpr(), ErrorExpr(), FalseExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, ErrorErrorErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({ErrorExpr(), ErrorExpr(), ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, ErrorErrorTrueIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({ErrorExpr(), ErrorExpr(), TrueExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, ErrorTrueFalseIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({ErrorExpr(), TrueExpr, FalseExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, ErrorTrueErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({ErrorExpr(), TrueExpr, ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, ErrorTrueTrueIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({ErrorExpr(), TrueExpr, TrueExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, TrueFalseFalseIsTrue) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({TrueExpr, FalseExpr, FalseExpr})), + Returns(Value(true))); // 1 true -> true +} +TEST_F(XorFunctionTest, TrueFalseErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({TrueExpr, FalseExpr, ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, TrueFalseTrueIsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::XorExpr({TrueExpr, FalseExpr, TrueExpr})), + Returns(Value(false))); // 2 true -> false +} +TEST_F(XorFunctionTest, TrueErrorFalseIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({TrueExpr, ErrorExpr(), FalseExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, TrueErrorErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({TrueExpr, ErrorExpr(), ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, TrueErrorTrueIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({TrueExpr, ErrorExpr(), TrueExpr}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, TrueTrueFalseIsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::XorExpr({TrueExpr, TrueExpr, FalseExpr})), + Returns(Value(false))); // 2 true -> false +} +TEST_F(XorFunctionTest, TrueTrueErrorIsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::XorExpr({TrueExpr, TrueExpr, ErrorExpr()}), + error_doc_), + ReturnsError()); +} +TEST_F(XorFunctionTest, TrueTrueTrueIsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::XorExpr({TrueExpr, TrueExpr, TrueExpr})), + Returns(Value(true))); // 3 true -> true +} + +// Nested +TEST_F(XorFunctionTest, NestedXor) { + auto child = testutil::XorExpr({TrueExpr, FalseExpr}); // child -> true + auto f = testutil::XorExpr({child, TrueExpr}); // xor(true, true) -> false + EXPECT_THAT(EvaluateExpr(*f), Returns(Value(false))); +} + +// Multiple Arguments (already covered by 3-operand tests) +TEST_F(XorFunctionTest, MultipleArguments) { + EXPECT_THAT(EvaluateExpr(*testutil::XorExpr({TrueExpr, FalseExpr, TrueExpr})), + Returns(Value(false))); // 2 true -> false +} + +// --- IsNull Tests --- +class IsNullFunctionTest : public LogicalExpressionsTest {}; + +TEST_F(IsNullFunctionTest, NullReturnsTrue) { + EXPECT_THAT(EvaluateExpr(*testutil::IsNullExpr(NullExpr)), + Returns(Value(true))); +} + +TEST_F(IsNullFunctionTest, ErrorReturnsError) { + EXPECT_THAT(EvaluateExpr(*testutil::IsNullExpr(ErrorExpr()), error_doc_), + ReturnsError()); +} + +TEST_F(IsNullFunctionTest, UnsetReturnsError) { + EXPECT_THAT(EvaluateExpr(*testutil::IsNullExpr(Field("non-existent-field"))), + ReturnsError()); +} + +TEST_F(IsNullFunctionTest, AnythingButNullReturnsFalse) { + // Use the test data from ComparisonValueTestData + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT(EvaluateExpr(*testutil::IsNullExpr(val)), + Returns(Value(false))); + } + // Explicitly test NaN as well + EXPECT_THAT(EvaluateExpr(*testutil::IsNullExpr(NanExpr)), + Returns(Value(false))); +} + +// --- IsNotNull Tests --- +class IsNotNullFunctionTest : public LogicalExpressionsTest {}; + +TEST_F(IsNotNullFunctionTest, NullReturnsFalse) { + EXPECT_THAT(EvaluateExpr(*testutil::IsNotNullExpr(NullExpr)), + Returns(Value(false))); +} + +TEST_F(IsNotNullFunctionTest, ErrorReturnsError) { + EXPECT_THAT(EvaluateExpr(*testutil::IsNotNullExpr(ErrorExpr()), error_doc_), + ReturnsError()); +} + +TEST_F(IsNotNullFunctionTest, UnsetReturnsError) { + EXPECT_THAT( + EvaluateExpr(*testutil::IsNotNullExpr(Field("non-existent-field"))), + ReturnsError()); +} + +TEST_F(IsNotNullFunctionTest, AnythingButNullReturnsTrue) { + // Use the test data from ComparisonValueTestData + for (const auto& val : + ComparisonValueTestData::AllSupportedComparableValues()) { + EXPECT_THAT(EvaluateExpr(*testutil::IsNotNullExpr(val)), + Returns(Value(true))); + } + // Explicitly test NaN as well + EXPECT_THAT(EvaluateExpr(*testutil::IsNotNullExpr(NanExpr)), + Returns(Value(true))); +} + +} // namespace core +} // 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 index 870b67f22e2..387e0a348e4 100644 --- a/Firestore/core/test/unit/testutil/expression_test_util.h +++ b/Firestore/core/test/unit/testutil/expression_test_util.h @@ -77,6 +77,10 @@ inline std::shared_ptr SharedConstant(double value) { return std::make_shared(Value(value)); } +inline std::shared_ptr SharedConstant(std::nullptr_t) { + return std::make_shared(Value(nullptr)); +} + inline std::shared_ptr SharedConstant(const char* value) { return std::make_shared(Value(value)); } @@ -196,6 +200,127 @@ inline std::shared_ptr GteExpr( "gte", std::vector>(params)); } +// --- Array Expression Helpers --- + +inline std::shared_ptr ArrayContainsAllExpr( + std::initializer_list> params) { + return std::make_shared( + "array_contains_all", std::vector>(params)); +} + +inline std::shared_ptr ArrayContainsAnyExpr( + std::initializer_list> params) { + return std::make_shared( + "array_contains_any", std::vector>(params)); +} + +inline std::shared_ptr ArrayContainsExpr( + std::initializer_list> params) { + return std::make_shared( + "array_contains", std::vector>(params)); +} + +inline std::shared_ptr ArrayLengthExpr(std::shared_ptr array_expr) { + return std::make_shared( + "array_length", std::vector>{array_expr}); +} + +// TODO(wuandy): Add ArrayConcatExpr, ArrayReverseExpr, ArrayElementExpr when +// needed. + +// --- Logical Expression Helpers --- + +inline std::shared_ptr AndExpr( + std::vector> operands) { + return std::make_shared("and", std::move(operands)); +} + +inline std::shared_ptr OrExpr( + std::vector> operands) { + return std::make_shared("or", std::move(operands)); +} + +inline std::shared_ptr XorExpr( + std::vector> operands) { + return std::make_shared("xor", std::move(operands)); +} + +// Note: NotExpr already exists below in Debugging section, reusing that one. + +inline std::shared_ptr CondExpr(std::shared_ptr condition, + std::shared_ptr true_case, + std::shared_ptr false_case) { + return std::make_shared( + "cond", + std::vector>{ + std::move(condition), std::move(true_case), std::move(false_case)}); +} + +inline std::shared_ptr EqAnyExpr(std::shared_ptr search, + std::shared_ptr values) { + std::vector> operands; + operands.push_back(std::move(search)); + operands.push_back(std::move(values)); + return std::make_shared("eq_any", std::move(operands)); +} + +inline std::shared_ptr NotEqAnyExpr(std::shared_ptr search, + std::shared_ptr values) { + std::vector> operands; + operands.push_back(std::move(search)); + operands.push_back(std::move(values)); + return std::make_shared("not_eq_any", std::move(operands)); +} + +inline std::shared_ptr IsNanExpr(std::shared_ptr operand) { + return std::make_shared( + "is_nan", std::vector>{std::move(operand)}); +} + +inline std::shared_ptr IsNotNanExpr(std::shared_ptr operand) { + return std::make_shared( + "is_not_nan", std::vector>{std::move(operand)}); +} + +inline std::shared_ptr IsNullExpr(std::shared_ptr operand) { + return std::make_shared( + "is_null", std::vector>{std::move(operand)}); +} + +inline std::shared_ptr IsNotNullExpr(std::shared_ptr operand) { + return std::make_shared( + "is_not_null", std::vector>{std::move(operand)}); +} + +inline std::shared_ptr IsErrorExpr(std::shared_ptr operand) { + return std::make_shared( + "is_error", std::vector>{std::move(operand)}); +} + +inline std::shared_ptr LogicalMaxExpr( + std::vector> operands) { + return std::make_shared("logical_maximum", std::move(operands)); +} + +inline std::shared_ptr LogicalMinExpr( + std::vector> operands) { + return std::make_shared("logical_minimum", std::move(operands)); +} + +// --- Debugging Expression Helpers --- + +inline std::shared_ptr ExistsExpr(std::shared_ptr param) { + return std::make_shared( + "exists", std::vector>{param}); +} + +// Note: NotExpr defined here, used by logical tests as well. +inline std::shared_ptr NotExpr(std::shared_ptr param) { + // Corrected to use FunctionExpr consistently + return std::make_shared( + "not", std::vector>{std::move(param)}); +} + // --- Comparison Test Data --- // Defines pairs of expressions for comparison testing.