Эх сурвалжийг харах

[realppl 3] Arithmetic and comparison expressions (#14849)

Remove Fuzzer
wu-hui 6 сар өмнө
parent
commit
bc6a400cbd

+ 50 - 20
Firestore/Example/Firestore.xcodeproj/project.pbxproj

@@ -25,6 +25,7 @@
 		022BA1619A576F6818B212C5 /* remote_store_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3B843E4A1F3930A400548890 /* remote_store_spec_test.json */; };
 		02C953A7B0FA5EF87DB0361A /* FSTIntegrationTestCase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5491BC711FB44593008B3588 /* FSTIntegrationTestCase.mm */; };
 		02EB33CC2590E1484D462912 /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; };
+		033A1FECDD47ED9B1891093B /* arithmetic_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */; };
 		035034AB3797D1E5E0112EC3 /* Validation_BloomFilterTest_MD5_1_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 3FDD0050CA08C8302400C5FB /* Validation_BloomFilterTest_MD5_1_1_bloom_filter_proto.json */; };
 		035DE410628A8F804F6F2790 /* target_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 526D755F65AC676234F57125 /* target_test.cc */; };
 		03AEB9E07A605AE1B5827548 /* field_index_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BF76A8DA34B5B67B4DD74666 /* field_index_test.cc */; };
@@ -127,7 +128,6 @@
 		11BC867491A6631D37DE56A8 /* async_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 872C92ABD71B12784A1C5520 /* async_testing.cc */; };
 		11EBD28DBD24063332433947 /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; };
 		11F8EE69182C9699E90A9E3D /* database_info_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB38D92E20235D22000A432D /* database_info_test.cc */; };
-		12158DFCEE09D24B7988A340 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; };
 		121F0FB9DCCBFB7573C7AF48 /* bundle_serializer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B5C2A94EE24E60543F62CC35 /* bundle_serializer_test.cc */; };
 		124AAEE987451820F24EEA8E /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CCC9BD953F121B9E29F9AA42 /* user_test.cc */; };
 		125B1048ECB755C2106802EB /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; };
@@ -140,7 +140,6 @@
 		12A611A85D59ED2742EEE187 /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 478DC75A0DCA6249A616DD30 /* Validation_BloomFilterTest_MD5_500_0001_membership_test_result.json */; };
 		12BB9ED1CA98AA52B92F497B /* log_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54C2294E1FECABAE007D065B /* log_test.cc */; };
 		12DB753599571E24DCED0C2C /* FIRValidationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06D202154D600B64F25 /* FIRValidationTests.mm */; };
-		12E04A12ABD5533B616D552A /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; };
 		132E3483789344640A52F223 /* reference_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 132E32997D781B896672D30A /* reference_set_test.cc */; };
 		1357806B4CD3A62A8F5DE86D /* http.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */; };
 		13D8F4196528BAB19DBB18A7 /* snapshot_version_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */; };
@@ -171,6 +170,7 @@
 		1733601ECCEA33E730DEAF45 /* autoid_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54740A521FC913E500713A1A /* autoid_test.cc */; };
 		17473086EBACB98CDC3CC65C /* view_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C7429071B33BDF80A7FA2F8A /* view_test.cc */; };
 		17638F813B9B556FE7718C0C /* FIRQuerySnapshotTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04F202154AA00B64F25 /* FIRQuerySnapshotTests.mm */; };
+		1792477DD2B3A1710BFD443F /* arithmetic_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */; };
 		17DC97DE15D200932174EC1F /* defer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8ABAC2E0402213D837F73DC3 /* defer_test.cc */; };
 		17DFF30CF61D87883986E8B6 /* executor_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4687208F9B9100554BA2 /* executor_std_test.cc */; };
 		17ECB768DA44AE0F49647E22 /* memory_query_engine_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8EF6A33BC2D84233C355F1D0 /* memory_query_engine_test.cc */; };
@@ -233,6 +233,7 @@
 		1F3DD2971C13CBBFA0D84866 /* memory_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 74FBEFA4FE4B12C435011763 /* memory_mutation_queue_test.cc */; };
 		1F4930A8366F74288121F627 /* create_noop_connectivity_monitor.cc in Sources */ = {isa = PBXBuildFile; fileRef = CF39535F2C41AB0006FA6C0E /* create_noop_connectivity_monitor.cc */; };
 		1F56F51EB6DF0951B1F4F85B /* lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 277EAACC4DD7C21332E8496A /* lru_garbage_collector_test.cc */; };
+		1F6319D85C1AFC0D81394470 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */; };
 		1F998DDECB54A66222CC66AA /* string_format_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54131E9620ADE678001DF3FF /* string_format_test.cc */; };
 		1FE23E911F0761AA896FAD67 /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = D8E530B27D5641B9C26A452C /* Validation_BloomFilterTest_MD5_500_1_bloom_filter_proto.json */; };
 		2045517602D767BD01EA71D9 /* overlay_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E1459FA70B8FC18DE4B80D0D /* overlay_test.cc */; };
@@ -281,6 +282,7 @@
 		26CB3D7C871BC56456C6021E /* timestamp_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = ABF6506B201131F8005F2C74 /* timestamp_test.cc */; };
 		276A563D546698B6AAC20164 /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; };
 		27AF4C4BAFE079892D4F5341 /* Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B3E4A77493524333133C5DC /* Validation_BloomFilterTest_MD5_50000_1_bloom_filter_proto.json */; };
+		27B652E6288A9CD1B99E618F /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */; };
 		27E46C94AAB087C80A97FF7F /* FIRServerTimestampTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E06E202154D600B64F25 /* FIRServerTimestampTests.mm */; };
 		280A282BE9AF4DCF4E855EAB /* filesystem_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F51859B394D01C0C507282F1 /* filesystem_test.cc */; };
 		2836CD14F6F0EA3B184E325E /* schedule_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9B0B005A79E765AF02793DCE /* schedule_test.cc */; };
@@ -494,6 +496,7 @@
 		4C5292BF643BF14FA2AC5DB1 /* settings_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = DD12BC1DB2480886D2FB0005 /* settings_test.cc */; };
 		4C66806697D7BCA730FA3697 /* common.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D221C2DDC800EFB9CC /* common.pb.cc */; };
 		4CDFF1AE3D639AA89C5C4411 /* query_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 731541602214AFFA0037F4DC /* query_spec_test.json */; };
+		4CF3DA15D4DF7D038BE13718 /* expression_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = AC64E6C629AAFAC92999B083 /* expression_test_util.cc */; };
 		4D1775B7916D4CDAD1BF1876 /* bundle.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = A366F6AE1A5A77548485C091 /* bundle.pb.cc */; };
 		4D20563D846FA0F3BEBFDE9D /* overlay_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E1459FA70B8FC18DE4B80D0D /* overlay_test.cc */; };
 		4D2655C5675D83205C3749DC /* fake_target_metadata_provider.cc in Sources */ = {isa = PBXBuildFile; fileRef = 71140E5D09C6E76F7C71B2FC /* fake_target_metadata_provider.cc */; };
@@ -517,6 +520,7 @@
 		4F5714D37B6D119CB07ED8AE /* orderby_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A21F315EE100DD57A1 /* orderby_spec_test.json */; };
 		4F65FD71B7960944C708A962 /* leveldb_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B629525F7A1AAC1AB765C74F /* leveldb_lru_garbage_collector_test.cc */; };
 		4F857404731D45F02C5EE4C3 /* async_queue_libdispatch_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4680208EA0BE00554BA2 /* async_queue_libdispatch_test.mm */; };
+		4F88E2D686CF4C150A29E84E /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */; };
 		4FAB27F13EA5D3D79E770EA2 /* ordered_code_benchmark.cc in Sources */ = {isa = PBXBuildFile; fileRef = 0473AFFF5567E667A125347B /* ordered_code_benchmark.cc */; };
 		4FAD8823DC37B9CA24379E85 /* leveldb_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C7942B6244F4C416B11B86C /* leveldb_mutation_queue_test.cc */; };
 		50059FDCD2DAAB755FEEEDF2 /* resource.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1C3F7302BF4AE6CBC00ECDD0 /* resource.pb.cc */; };
@@ -749,7 +753,6 @@
 		6156C6A837D78D49ED8B8812 /* index_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 8C7278B604B8799F074F4E8C /* index_spec_test.json */; };
 		6161B5032047140C00A99DBB /* FIRFirestoreSourceTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6161B5012047140400A99DBB /* FIRFirestoreSourceTests.mm */; };
 		618BBEA620B89AAC00B5BCE7 /* target.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */; };
-		618BBEA720B89AAC00B5BCE7 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; };
 		618BBEA820B89AAC00B5BCE7 /* mutation.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */; };
 		618BBEAE20B89AAC00B5BCE7 /* latlng.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9220B89AAC00B5BCE7 /* latlng.pb.cc */; };
 		618BBEAF20B89AAC00B5BCE7 /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; };
@@ -843,7 +846,6 @@
 		6F256C06FCBA46378EC35D72 /* leveldb_overlay_migration_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D8A6D52723B1BABE1B7B8D8F /* leveldb_overlay_migration_manager_test.cc */; };
 		6F3CAC76D918D6B0917EDF92 /* query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B9C261C26C5D311E1E3C0CB9 /* query_test.cc */; };
 		6F45846C159D3C063DBD3CBE /* FirestoreEncoderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1235769422B86E65007DDFA9 /* FirestoreEncoderTests.swift */; };
-		6F511ABFD023AEB81F92DB12 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; };
 		6F67601562343B63B8996F7A /* FSTTestingHooks.mm in Sources */ = {isa = PBXBuildFile; fileRef = D85AC18C55650ED230A71B82 /* FSTTestingHooks.mm */; };
 		6F914209F46E6552B5A79570 /* async_queue_std_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6FB4681208EA0BE00554BA2 /* async_queue_std_test.cc */; };
 		6FAC16B7FBD3B40D11A6A816 /* target.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */; };
@@ -977,6 +979,7 @@
 		851346D66DEC223E839E3AA9 /* memory_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 74FBEFA4FE4B12C435011763 /* memory_mutation_queue_test.cc */; };
 		856A1EAAD674ADBDAAEDAC37 /* bundle_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */; };
 		85A33A9CE33207C2333DDD32 /* FIRTransactionOptionsTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = CF39ECA1293D21A0A2AB2626 /* FIRTransactionOptionsTests.mm */; };
+		85ADFEB234EBE3D9CDFFCE12 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */; };
 		85B8918FC8C5DC62482E39C3 /* resource_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2B02024FFD70028D6BE /* resource_path_test.cc */; };
 		85BC2AB572A400114BF59255 /* limbo_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA129E1F315EE100DD57A1 /* limbo_spec_test.json */; };
 		85D61BDC7FB99B6E0DD3AFCA /* mutation.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */; };
@@ -997,7 +1000,7 @@
 		87B5AC3EBF0E83166B142FA4 /* string_apple_benchmark.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4C73C0CC6F62A90D8573F383 /* string_apple_benchmark.mm */; };
 		881E55152AB34465412F8542 /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; };
 		88929ED628DA8DD9592974ED /* task_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 899FC22684B0F7BEEAE13527 /* task_test.cc */; };
-		88FD82A1FC5FEC5D56B481D8 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; };
+		8976F3D5515C4A784EC6627F /* arithmetic_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */; };
 		897F3C1936612ACB018CA1DD /* http.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9720B89AAC00B5BCE7 /* http.pb.cc */; };
 		89C71AEAA5316836BB1D5A01 /* view_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = C7429071B33BDF80A7FA2F8A /* view_test.cc */; };
 		89EB0C7B1241E6F1800A3C7E /* empty_credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8FA60B08D59FEA0D6751E87F /* empty_credentials_provider_test.cc */; };
@@ -1068,6 +1071,7 @@
 		977E0DA564D6EAF975A4A1A0 /* settings_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = DD12BC1DB2480886D2FB0005 /* settings_test.cc */; };
 		9783FAEA4CF758E8C4C2D76E /* hashing_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54511E8D209805F8005BD28F /* hashing_test.cc */; };
 		978D9EFDC56CC2E1FA468712 /* leveldb_snappy_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D9D94300B9C02F7069523C00 /* leveldb_snappy_test.cc */; };
+		979840A404FAB985B1D41AA6 /* expression_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = AC64E6C629AAFAC92999B083 /* expression_test_util.cc */; };
 		9860F493EBF43AF5AC0A88BD /* empty_credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8FA60B08D59FEA0D6751E87F /* empty_credentials_provider_test.cc */; };
 		98708140787A9465D883EEC9 /* leveldb_mutation_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5C7942B6244F4C416B11B86C /* leveldb_mutation_queue_test.cc */; };
 		98FE82875A899A40A98AAC22 /* leveldb_opener_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */; };
@@ -1311,6 +1315,7 @@
 		BDDAE67000DBF10E9EA7FED0 /* nanopb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */; };
 		BDF3A6C121F2773BB3A347A7 /* counting_query_engine.cc in Sources */ = {isa = PBXBuildFile; fileRef = 99434327614FEFF7F7DC88EC /* counting_query_engine.cc */; };
 		BE1D7C7E413449AFFBA21BCB /* overlay_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = E1459FA70B8FC18DE4B80D0D /* overlay_test.cc */; };
+		BE4C2DFCEEFDC1DC0B37533D /* arithmetic_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */; };
 		BE767D2312D2BE84484309A0 /* event_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F57521E161450FAF89075ED /* event_manager_test.cc */; };
 		BE92E16A9B9B7AD5EB072919 /* string_format_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 9CFD366B783AE27B9E79EE7A /* string_format_apple_test.mm */; };
 		BEE0294A23AB993E5DE0E946 /* leveldb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 332485C4DCC6BA0DBB5E31B7 /* leveldb_util_test.cc */; };
@@ -1328,7 +1333,6 @@
 		C10417B067155BE78E19807D /* FIRIndexingTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 795AA8FC31D2AF6864B07D39 /* FIRIndexingTests.mm */; };
 		C1237EE2A74F174A3DF5978B /* memory_target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2286F308EFB0534B1BDE05B9 /* memory_target_cache_test.cc */; };
 		C15F5F1E7427738F20C2D789 /* offline_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 54DA12A11F315EE100DD57A1 /* offline_spec_test.json */; };
-		C19214F5B43AA745A7FC2FC1 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */; };
 		C1B4621C0820EEB0AC9CCD22 /* bits_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380D01201BC69F00D97691 /* bits_test.cc */; };
 		C1C3369C7ECE069B76A84AD1 /* Validation_BloomFilterTest_MD5_500_1_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 8AB49283E544497A9C5A0E59 /* Validation_BloomFilterTest_MD5_500_1_membership_test_result.json */; };
 		C1CD78F1FDE0918B4F87BC6F /* empty_credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8FA60B08D59FEA0D6751E87F /* empty_credentials_provider_test.cc */; };
@@ -1428,6 +1432,7 @@
 		D3CB03747E34D7C0365638F1 /* transform_operation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 33607A3AE91548BD219EC9C6 /* transform_operation_test.cc */; };
 		D4572060A0FD4D448470D329 /* leveldb_transaction_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 88CF09277CFA45EE1273E3BA /* leveldb_transaction_test.cc */; };
 		D4D8BA32ACC5C2B1B29711C0 /* memory_lru_garbage_collector_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 9765D47FA12FA283F4EFAD02 /* memory_lru_garbage_collector_test.cc */; };
+		D4E02FF9F4D517BF5D4F2D14 /* arithmetic_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */; };
 		D4F85AEACD2FD03C738D1052 /* Validation_BloomFilterTest_MD5_1_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 5C68EE4CB94C0DD6E333F546 /* Validation_BloomFilterTest_MD5_1_01_membership_test_result.json */; };
 		D50232D696F19C2881AC01CE /* token_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A082AFDD981B07B5AD78FDE8 /* token_test.cc */; };
 		D550446303227FB1B381133C /* FSTAPIHelpers.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04E202154AA00B64F25 /* FSTAPIHelpers.mm */; };
@@ -1475,12 +1480,14 @@
 		DC0B0E50DBAE916E6565AA18 /* string_win_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 79507DF8378D3C42F5B36268 /* string_win_test.cc */; };
 		DC0E186BDD221EAE9E4D2F41 /* sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4E20A36DBB00BCEB75 /* sorted_map_test.cc */; };
 		DC1C711290E12F8EF3601151 /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; };
+		DC3351455F8753678905CF73 /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */; };
 		DC48407370E87F2233D7AB7E /* statusor_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352D20A3B3D7003E0143 /* statusor_test.cc */; };
 		DC6804424FC8F7B3044DD0BB /* random_access_queue_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 014C60628830D95031574D15 /* random_access_queue_test.cc */; };
 		DCC8F3D4AA87C81AB3FD9491 /* md5_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3D050936A2D52257FD17FB6E /* md5_test.cc */; };
 		DCD83C545D764FB15FD88B02 /* counting_query_engine.cc in Sources */ = {isa = PBXBuildFile; fileRef = 99434327614FEFF7F7DC88EC /* counting_query_engine.cc */; };
 		DD04F7FE7A1ADE230A247DBC /* byte_stream_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7628664347B9C96462D4BF17 /* byte_stream_apple_test.mm */; };
 		DD0F288108714D5A406D0A9F /* Validation_BloomFilterTest_MD5_1_01_membership_test_result.json in Resources */ = {isa = PBXBuildFile; fileRef = 5C68EE4CB94C0DD6E333F546 /* Validation_BloomFilterTest_MD5_1_01_membership_test_result.json */; };
+		DD175F74AC25CC419E874A1D /* maybe_document.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 28034BA61A7395543F1508B3 /* maybe_document.pb.cc */; };
 		DD5976A45071455FF3FE74B8 /* string_win_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 79507DF8378D3C42F5B36268 /* string_win_test.cc */; };
 		DD6C480629B3F87933FAF440 /* filesystem_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = BA02DA2FCD0001CFC6EB08DA /* filesystem_testing.cc */; };
 		DD935E243A64A4EB688E4C1C /* credentials_provider_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2F4FA4576525144C5069A7A5 /* credentials_provider_test.cc */; };
@@ -1488,6 +1495,7 @@
 		DDC782CBA37AA9B0EA373B7A /* explain_stats.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 428662F00938E9E21F7080D7 /* explain_stats.pb.cc */; };
 		DDD219222EEE13E3F9F2C703 /* leveldb_transaction_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 88CF09277CFA45EE1273E3BA /* leveldb_transaction_test.cc */; };
 		DDDE74C752E65DE7D39A7166 /* view_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = A5466E7809AD2871FFDE6C76 /* view_testing.cc */; };
+		DDED4752521AF8B347EB6E99 /* expression_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = AC64E6C629AAFAC92999B083 /* expression_test_util.cc */; };
 		DE03B2D41F2149D600A30B9C /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
 		DE03B2D51F2149D600A30B9C /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
 		DE03B2D61F2149D600A30B9C /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
@@ -1556,6 +1564,7 @@
 		E884336B43BBD1194C17E3C4 /* status_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3CAA33F964042646FDDAF9F9 /* status_testing.cc */; };
 		E8AB8024B70F6C960D8C7530 /* document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */; };
 		E8BA7055EDB8B03CC99A528F /* recovery_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 9C1AFCC9E616EC33D6E169CF /* recovery_spec_test.json */; };
+		E8BB7CCF3928A5866B1C9B86 /* arithmetic_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 76EED4ED84056B623D92FE20 /* arithmetic_test.cc */; };
 		E9071BE412DC42300B936BAF /* explain_stats.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 428662F00938E9E21F7080D7 /* explain_stats.pb.cc */; };
 		E962CA641FB1312638593131 /* leveldb_document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */; };
 		E99D5467483B746D4AA44F74 /* fields_array_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BA4CBA48204C9E25B56993BC /* fields_array_test.cc */; };
@@ -1573,6 +1582,7 @@
 		EBE4A7B6A57BCE02B389E8A6 /* byte_string_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 5342CDDB137B4E93E2E85CCA /* byte_string_test.cc */; };
 		EBFC611B1BF195D0EC710AF4 /* app_testing.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5467FB07203E6A44009C9584 /* app_testing.mm */; };
 		EC160876D8A42166440E0B53 /* FIRCursorTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E070202154D600B64F25 /* FIRCursorTests.mm */; };
+		EC1C68ADCA37BFF885671D7A /* expression_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = AC64E6C629AAFAC92999B083 /* expression_test_util.cc */; };
 		EC3331B17394886A3715CFD8 /* target.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */; };
 		EC62F9E29CE3598881908FB8 /* leveldb_transaction_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 88CF09277CFA45EE1273E3BA /* leveldb_transaction_test.cc */; };
 		EC63BD5E46C8734B6D20312D /* Validation_BloomFilterTest_MD5_50000_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 7B44DD11682C4803B73DCC34 /* Validation_BloomFilterTest_MD5_50000_01_bloom_filter_proto.json */; };
@@ -1632,6 +1642,7 @@
 		F3DEF2DB11FADAABDAA4C8BB /* bundle_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */; };
 		F3F09BC931A717CEFF4E14B9 /* FIRFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04A202154AA00B64F25 /* FIRFieldValueTests.mm */; };
 		F481368DB694B3B4D0C8E4A2 /* query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B9C261C26C5D311E1E3C0CB9 /* query_test.cc */; };
+		F4DD8315F7F85F9CAB2E7206 /* expression_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = AC64E6C629AAFAC92999B083 /* expression_test_util.cc */; };
 		F4F00BF4E87D7F0F0F8831DB /* FSTEventAccumulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */; };
 		F4FAC5A7D40A0A9A3EA77998 /* FSTLevelDBSpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02C20213FFB00B64F25 /* FSTLevelDBSpecTests.mm */; };
 		F563446799EFCF4916758E6C /* Validation_BloomFilterTest_MD5_50000_01_bloom_filter_proto.json in Resources */ = {isa = PBXBuildFile; fileRef = 7B44DD11682C4803B73DCC34 /* Validation_BloomFilterTest_MD5_50000_01_bloom_filter_proto.json */; };
@@ -1674,6 +1685,7 @@
 		FC1D22B6EC4E5F089AE39B8C /* memory_target_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 2286F308EFB0534B1BDE05B9 /* memory_target_cache_test.cc */; };
 		FC6C9D1A8B24A5C9507272F7 /* globals_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4564AD9C55EC39C080EB9476 /* globals_cache_test.cc */; };
 		FCA48FB54FC50BFDFDA672CD /* array_sorted_map_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54EB764C202277B30088B8F3 /* array_sorted_map_test.cc */; };
+		FCBD7D902CEB2A263AF2DE55 /* expression_test_util.cc in Sources */ = {isa = PBXBuildFile; fileRef = AC64E6C629AAFAC92999B083 /* expression_test_util.cc */; };
 		FCF8E7F5268F6842C07B69CF /* write.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 544129D921C2DDC800EFB9CC /* write.pb.cc */; };
 		FD365D6DFE9511D3BA2C74DF /* hard_assert_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 444B7AB3F5A2929070CB1363 /* hard_assert_test.cc */; };
 		FD6F5B4497D670330E7F89DA /* document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = FFCA39825D9678A03D1845D0 /* document_overlay_cache_test.cc */; };
@@ -1782,6 +1794,7 @@
 		253A7A96FFAA2C8A8754D3CF /* Pods_Firestore_IntegrationTests_macOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_macOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		26DDBA115DEB88631B93F203 /* thread_safe_memoizer_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = thread_safe_memoizer_testing.h; sourceTree = "<group>"; };
 		277EAACC4DD7C21332E8496A /* lru_garbage_collector_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = lru_garbage_collector_test.cc; sourceTree = "<group>"; };
+		28034BA61A7395543F1508B3 /* maybe_document.pb.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = maybe_document.pb.cc; sourceTree = "<group>"; };
 		28B45B2104E2DAFBBF86DBB7 /* logic_utils_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = logic_utils_test.cc; sourceTree = "<group>"; };
 		29749DC3DADA38CAD1EB9AC4 /* Pods-Firestore_Tests_macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_macOS.debug.xcconfig"; path = "Target Support Files/Pods-Firestore_Tests_macOS/Pods-Firestore_Tests_macOS.debug.xcconfig"; sourceTree = "<group>"; };
 		29D9C76922DAC6F710BC1EF4 /* memory_document_overlay_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_document_overlay_cache_test.cc; sourceTree = "<group>"; };
@@ -1963,7 +1976,6 @@
 		600A7D7D821CE84E0CA8CB89 /* async_testing.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = async_testing.h; sourceTree = "<group>"; };
 		6161B5012047140400A99DBB /* FIRFirestoreSourceTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRFirestoreSourceTests.mm; sourceTree = "<group>"; };
 		618BBE7D20B89AAC00B5BCE7 /* target.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = target.pb.cc; sourceTree = "<group>"; };
-		618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = maybe_document.pb.cc; sourceTree = "<group>"; };
 		618BBE7F20B89AAC00B5BCE7 /* target.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = target.pb.h; sourceTree = "<group>"; };
 		618BBE8020B89AAC00B5BCE7 /* maybe_document.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = maybe_document.pb.h; sourceTree = "<group>"; };
 		618BBE8120B89AAC00B5BCE7 /* mutation.pb.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mutation.pb.h; sourceTree = "<group>"; };
@@ -2012,6 +2024,7 @@
 		75860CD13AF47EB1EA39EC2F /* leveldb_opener_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_opener_test.cc; sourceTree = "<group>"; };
 		75E24C5CD7BC423D48713100 /* counting_query_engine.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = counting_query_engine.h; sourceTree = "<group>"; };
 		7628664347B9C96462D4BF17 /* byte_stream_apple_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = byte_stream_apple_test.mm; sourceTree = "<group>"; };
+		76EED4ED84056B623D92FE20 /* arithmetic_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; name = arithmetic_test.cc; path = expressions/arithmetic_test.cc; sourceTree = "<group>"; };
 		776530F066E788C355B78457 /* FIRBundlesTests.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRBundlesTests.mm; sourceTree = "<group>"; };
 		78EE0BFC7E60C4929458A0EA /* resource.pb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = resource.pb.h; sourceTree = "<group>"; };
 		79507DF8378D3C42F5B36268 /* string_win_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = string_win_test.cc; sourceTree = "<group>"; };
@@ -2083,6 +2096,7 @@
 		AB7BAB332012B519001E0872 /* geo_point_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = geo_point_test.cc; sourceTree = "<group>"; };
 		ABA495B9202B7E79008A7851 /* snapshot_version_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = snapshot_version_test.cc; sourceTree = "<group>"; };
 		ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = timestamp_test.cc; sourceTree = "<group>"; };
+		AC64E6C629AAFAC92999B083 /* expression_test_util.cc */ = {isa = PBXFileReference; includeInIndex = 1; path = expression_test_util.cc; sourceTree = "<group>"; };
 		AE4A9E38D65688EE000EE2A1 /* index_manager_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = index_manager_test.cc; sourceTree = "<group>"; };
 		AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_document_overlay_cache_test.cc; sourceTree = "<group>"; };
 		AF924C79F49F793992A84879 /* aggregate_query_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = aggregate_query_test.cc; path = api/aggregate_query_test.cc; sourceTree = "<group>"; };
@@ -2130,6 +2144,7 @@
 		CC572A9168BBEF7B83E4BBC5 /* view_snapshot_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = view_snapshot_test.cc; sourceTree = "<group>"; };
 		CCC9BD953F121B9E29F9AA42 /* user_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = user_test.cc; path = credentials/user_test.cc; sourceTree = "<group>"; };
 		CD422AF3E4515FB8E9BE67A0 /* equals_tester.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = equals_tester.h; sourceTree = "<group>"; };
+		CDC018C1D4CEC9B131449F98 /* expression_test_util.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = expression_test_util.h; sourceTree = "<group>"; };
 		CE37875365497FFA8687B745 /* message_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = message_test.cc; path = nanopb/message_test.cc; sourceTree = "<group>"; };
 		CF39535F2C41AB0006FA6C0E /* create_noop_connectivity_monitor.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = create_noop_connectivity_monitor.cc; sourceTree = "<group>"; };
 		CF39ECA1293D21A0A2AB2626 /* FIRTransactionOptionsTests.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRTransactionOptionsTests.mm; sourceTree = "<group>"; };
@@ -2391,6 +2406,8 @@
 				4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */,
 				84076EADF6872C78CDAC7291 /* bundle_builder.h */,
 				CD422AF3E4515FB8E9BE67A0 /* equals_tester.h */,
+				AC64E6C629AAFAC92999B083 /* expression_test_util.cc */,
+				CDC018C1D4CEC9B131449F98 /* expression_test_util.h */,
 				BA02DA2FCD0001CFC6EB08DA /* filesystem_testing.cc */,
 				64AA92CFA356A2360F3C5646 /* filesystem_testing.h */,
 				E2E39422953DE1D3C7B97E77 /* md5_testing.cc */,
@@ -2787,7 +2804,7 @@
 		618BBE7C20B89AAC00B5BCE7 /* local */ = {
 			isa = PBXGroup;
 			children = (
-				618BBE7E20B89AAC00B5BCE7 /* maybe_document.pb.cc */,
+				28034BA61A7395543F1508B3 /* maybe_document.pb.cc */,
 				618BBE8020B89AAC00B5BCE7 /* maybe_document.pb.h */,
 				618BBE8220B89AAC00B5BCE7 /* mutation.pb.cc */,
 				618BBE8120B89AAC00B5BCE7 /* mutation.pb.h */,
@@ -3013,6 +3030,7 @@
 		AD2E6E1CDE874DD15298E8F5 /* expressions */ = {
 			isa = PBXGroup;
 			children = (
+				76EED4ED84056B623D92FE20 /* arithmetic_test.cc */,
 				87DD1A65EBA9FFC1FFAAE657 /* comparison_test.cc */,
 			);
 			name = expressions;
@@ -4291,6 +4309,7 @@
 				C8BA36C8B5E26C173F91E677 /* aggregation_result.pb.cc in Sources */,
 				45939AFF906155EA27D281AB /* annotations.pb.cc in Sources */,
 				FF3405218188DFCE586FB26B /* app_testing.mm in Sources */,
+				E8BB7CCF3928A5866B1C9B86 /* arithmetic_test.cc in Sources */,
 				B192F30DECA8C28007F9B1D0 /* array_sorted_map_test.cc in Sources */,
 				4F857404731D45F02C5EE4C3 /* async_queue_libdispatch_test.mm in Sources */,
 				83A9CD3B6E791A860CE81FA1 /* async_queue_std_test.cc in Sources */,
@@ -4313,8 +4332,8 @@
 				9AC604BF7A76CABDF26F8C8E /* cc_compilation_test.cc in Sources */,
 				1B730A4E8C4BD7B5B0FF9C7F /* collection_test.cc in Sources */,
 				5556B648B9B1C2F79A706B4F /* common.pb.cc in Sources */,
-				08D853C9D3A4DC919C55671A /* comparison_test.cc in Sources */,
 				11627F3A48F710D654829807 /* comparison_test.cc in Sources */,
+				08D853C9D3A4DC919C55671A /* comparison_test.cc in Sources */,
 				3095316962A00DD6A4A2A441 /* counting_query_engine.cc in Sources */,
 				4D903ED7B7E4D38F988CD3F8 /* create_noop_connectivity_monitor.cc in Sources */,
 				9BEC62D59EB2C68342F493CD /* credentials_provider_test.cc in Sources */,
@@ -4335,6 +4354,7 @@
 				470A37727BBF516B05ED276A /* executor_test.cc in Sources */,
 				2F72DBE2EC6E24A81C69DEF0 /* explain_stats.pb.cc in Sources */,
 				2E0BBA7E627EB240BA11B0D0 /* exponential_backoff_test.cc in Sources */,
+				FCBD7D902CEB2A263AF2DE55 /* expression_test_util.cc in Sources */,
 				9009C285F418EA80C46CF06B /* fake_target_metadata_provider.cc in Sources */,
 				7B58861D0978827BC4CB1DFA /* field_behavior.pb.cc in Sources */,
 				2E373EA9D5FF8C6DE2507675 /* field_index_test.cc in Sources */,
@@ -4387,7 +4407,7 @@
 				DBDC8E997E909804F1B43E92 /* log_test.cc in Sources */,
 				F924DF3D9DCD2720C315A372 /* logic_utils_test.cc in Sources */,
 				3F6C9F8A993CF4B0CD51E7F0 /* lru_garbage_collector_test.cc in Sources */,
-				12158DFCEE09D24B7988A340 /* maybe_document.pb.cc in Sources */,
+				1F6319D85C1AFC0D81394470 /* maybe_document.pb.cc in Sources */,
 				380E543B7BC6F648BBB250B4 /* md5_test.cc in Sources */,
 				FE20E696E014CDCE918E91D6 /* md5_testing.cc in Sources */,
 				FA43BA0195DA90CE29B29D36 /* memory_bundle_cache_test.cc in Sources */,
@@ -4519,6 +4539,7 @@
 				156429A2993B86A905A42D96 /* aggregation_result.pb.cc in Sources */,
 				1C19D796DB6715368407387A /* annotations.pb.cc in Sources */,
 				6EEA00A737690EF82A3C91C6 /* app_testing.mm in Sources */,
+				033A1FECDD47ED9B1891093B /* arithmetic_test.cc in Sources */,
 				1291D9F5300AFACD1FBD262D /* array_sorted_map_test.cc in Sources */,
 				4AD9809C9CE9FA09AC40992F /* async_queue_libdispatch_test.mm in Sources */,
 				38208AC761FF994BA69822BE /* async_queue_std_test.cc in Sources */,
@@ -4541,8 +4562,8 @@
 				079E63E270F3EFCA175D2705 /* cc_compilation_test.cc in Sources */,
 				0480559E91BB66732ABE45C8 /* collection_test.cc in Sources */,
 				18638EAED9E126FC5D895B14 /* common.pb.cc in Sources */,
-				1115DB1F1DCE93B63E03BA8C /* comparison_test.cc in Sources */,
 				6888F84253360455023C600B /* comparison_test.cc in Sources */,
+				1115DB1F1DCE93B63E03BA8C /* comparison_test.cc in Sources */,
 				2A0925323776AD50C1105BC0 /* counting_query_engine.cc in Sources */,
 				AEE9105543013C9C89FAB2B5 /* create_noop_connectivity_monitor.cc in Sources */,
 				B6BF87E3C9A72DCB8C5DB754 /* credentials_provider_test.cc in Sources */,
@@ -4563,6 +4584,7 @@
 				3A7CB01751697ED599F2D9A1 /* executor_test.cc in Sources */,
 				7CAF0E8C47FB2DD486240D47 /* explain_stats.pb.cc in Sources */,
 				EF3518F84255BAF3EBD317F6 /* exponential_backoff_test.cc in Sources */,
+				979840A404FAB985B1D41AA6 /* expression_test_util.cc in Sources */,
 				4DAFC3A3FD5E96910A517320 /* fake_target_metadata_provider.cc in Sources */,
 				E9BC6A5BC2B209B1BA2F8BD6 /* field_behavior.pb.cc in Sources */,
 				69D3AD697D1A7BF803A08160 /* field_index_test.cc in Sources */,
@@ -4615,7 +4637,7 @@
 				12BB9ED1CA98AA52B92F497B /* log_test.cc in Sources */,
 				7EF56BA2A480026D62CCA35A /* logic_utils_test.cc in Sources */,
 				1F56F51EB6DF0951B1F4F85B /* lru_garbage_collector_test.cc in Sources */,
-				88FD82A1FC5FEC5D56B481D8 /* maybe_document.pb.cc in Sources */,
+				DD175F74AC25CC419E874A1D /* maybe_document.pb.cc in Sources */,
 				DCC8F3D4AA87C81AB3FD9491 /* md5_test.cc in Sources */,
 				169EDCF15637580BA79B61AD /* md5_testing.cc in Sources */,
 				9611A0FAA2E10A6B1C1AC2EA /* memory_bundle_cache_test.cc in Sources */,
@@ -4774,6 +4796,7 @@
 				0EC3921AE220410F7394729B /* aggregation_result.pb.cc in Sources */,
 				276A563D546698B6AAC20164 /* annotations.pb.cc in Sources */,
 				7B8D7BAC1A075DB773230505 /* app_testing.mm in Sources */,
+				8976F3D5515C4A784EC6627F /* arithmetic_test.cc in Sources */,
 				DC1C711290E12F8EF3601151 /* array_sorted_map_test.cc in Sources */,
 				9B2CD4CBB1DFE8BC3C81A335 /* async_queue_libdispatch_test.mm in Sources */,
 				342724CA250A65E23CB133AC /* async_queue_std_test.cc in Sources */,
@@ -4796,8 +4819,8 @@
 				0A52B47C43B7602EE64F53A7 /* cc_compilation_test.cc in Sources */,
 				064689971747DA312770AB7A /* collection_test.cc in Sources */,
 				1DB3013C5FC736B519CD65A3 /* common.pb.cc in Sources */,
-				555161D6DB2DDC8B57F72A70 /* comparison_test.cc in Sources */,
 				99F97B28DA546D42AB14214B /* comparison_test.cc in Sources */,
+				555161D6DB2DDC8B57F72A70 /* comparison_test.cc in Sources */,
 				7394B5C29C6E524C2AF964E6 /* counting_query_engine.cc in Sources */,
 				C02A969BF4BB63ABCB531B4B /* create_noop_connectivity_monitor.cc in Sources */,
 				DD935E243A64A4EB688E4C1C /* credentials_provider_test.cc in Sources */,
@@ -4818,6 +4841,7 @@
 				18F644E6AA98E6D6F3F1F809 /* executor_test.cc in Sources */,
 				ABE599C3BF9FB6AFF18AA901 /* explain_stats.pb.cc in Sources */,
 				6938575C8B5E6FE0D562547A /* exponential_backoff_test.cc in Sources */,
+				4CF3DA15D4DF7D038BE13718 /* expression_test_util.cc in Sources */,
 				258B372CF33B7E7984BBA659 /* fake_target_metadata_provider.cc in Sources */,
 				2FC2B732841BF2C425EB35DF /* field_behavior.pb.cc in Sources */,
 				F8BD2F61EFA35C2D5120D9EB /* field_index_test.cc in Sources */,
@@ -4870,7 +4894,7 @@
 				CAFB1E0ED514FEF4641E3605 /* log_test.cc in Sources */,
 				0595B5EBEB8F09952B72C883 /* logic_utils_test.cc in Sources */,
 				913F6E57AF18F84C5ECFD414 /* lru_garbage_collector_test.cc in Sources */,
-				6F511ABFD023AEB81F92DB12 /* maybe_document.pb.cc in Sources */,
+				27B652E6288A9CD1B99E618F /* maybe_document.pb.cc in Sources */,
 				13ED75EFC2F6917951518A4B /* md5_test.cc in Sources */,
 				E2AC3BDAAFFF9A45C916708B /* md5_testing.cc in Sources */,
 				FF6333B8BD9732C068157221 /* memory_bundle_cache_test.cc in Sources */,
@@ -5029,6 +5053,7 @@
 				DF983A9C1FBF758AF3AF110D /* aggregation_result.pb.cc in Sources */,
 				EA46611779C3EEF12822508C /* annotations.pb.cc in Sources */,
 				8F4F40E9BC7ED588F67734D5 /* app_testing.mm in Sources */,
+				BE4C2DFCEEFDC1DC0B37533D /* arithmetic_test.cc in Sources */,
 				A6E236CE8B3A47BE32254436 /* array_sorted_map_test.cc in Sources */,
 				1CB8AEFBF3E9565FF9955B50 /* async_queue_libdispatch_test.mm in Sources */,
 				AB2BAB0BD77FF05CC26FCF75 /* async_queue_std_test.cc in Sources */,
@@ -5051,8 +5076,8 @@
 				1E8A00ABF414AC6C6591D9AC /* cc_compilation_test.cc in Sources */,
 				C87DF880BADEA1CBF8365700 /* collection_test.cc in Sources */,
 				1D71CA6BBA1E3433F243188E /* common.pb.cc in Sources */,
-				9C86EEDEA131BFD50255EEF1 /* comparison_test.cc in Sources */,
 				476AE05E0878007DE1BF5460 /* comparison_test.cc in Sources */,
+				9C86EEDEA131BFD50255EEF1 /* comparison_test.cc in Sources */,
 				DCD83C545D764FB15FD88B02 /* counting_query_engine.cc in Sources */,
 				ECC433628575AE994C621C54 /* create_noop_connectivity_monitor.cc in Sources */,
 				6E7603BC1D8011A5D6F62072 /* credentials_provider_test.cc in Sources */,
@@ -5073,6 +5098,7 @@
 				814724DE70EFC3DDF439CD78 /* executor_test.cc in Sources */,
 				A296B0110550890E1D8D59A3 /* explain_stats.pb.cc in Sources */,
 				BD6CC8614970A3D7D2CF0D49 /* exponential_backoff_test.cc in Sources */,
+				DDED4752521AF8B347EB6E99 /* expression_test_util.cc in Sources */,
 				4D2655C5675D83205C3749DC /* fake_target_metadata_provider.cc in Sources */,
 				FB462B2C6D3C167DF32BA0E1 /* field_behavior.pb.cc in Sources */,
 				50C852E08626CFA7DC889EEA /* field_index_test.cc in Sources */,
@@ -5125,7 +5151,7 @@
 				6B94E0AE1002C5C9EA0F5582 /* log_test.cc in Sources */,
 				0D6AE96565603226DB2E6838 /* logic_utils_test.cc in Sources */,
 				95CE3F5265B9BB7297EE5A6B /* lru_garbage_collector_test.cc in Sources */,
-				C19214F5B43AA745A7FC2FC1 /* maybe_document.pb.cc in Sources */,
+				4F88E2D686CF4C150A29E84E /* maybe_document.pb.cc in Sources */,
 				211A60ECA3976D27C0BF59BB /* md5_test.cc in Sources */,
 				E72A77095FF6814267DF0F6D /* md5_testing.cc in Sources */,
 				94854FAEAEA75A1AC77A0515 /* memory_bundle_cache_test.cc in Sources */,
@@ -5267,6 +5293,7 @@
 				B81B6F327B5E3FE820DC3FB3 /* aggregation_result.pb.cc in Sources */,
 				618BBEAF20B89AAC00B5BCE7 /* annotations.pb.cc in Sources */,
 				5467FB08203E6A44009C9584 /* app_testing.mm in Sources */,
+				D4E02FF9F4D517BF5D4F2D14 /* arithmetic_test.cc in Sources */,
 				54EB764D202277B30088B8F3 /* array_sorted_map_test.cc in Sources */,
 				B6FB4684208EA0EC00554BA2 /* async_queue_libdispatch_test.mm in Sources */,
 				B6FB4685208EA0F000554BA2 /* async_queue_std_test.cc in Sources */,
@@ -5289,8 +5316,8 @@
 				08A9C531265B5E4C5367346E /* cc_compilation_test.cc in Sources */,
 				C551536B0BAE9EB452DD6758 /* collection_test.cc in Sources */,
 				544129DA21C2DDC800EFB9CC /* common.pb.cc in Sources */,
-				548DB929200D59F600E00ABC /* comparison_test.cc in Sources */,
 				95490163C98C4F8AFD019730 /* comparison_test.cc in Sources */,
+				548DB929200D59F600E00ABC /* comparison_test.cc in Sources */,
 				4E2E0314F9FDD7BCED60254A /* counting_query_engine.cc in Sources */,
 				1989623826923A9D5A7EFA40 /* create_noop_connectivity_monitor.cc in Sources */,
 				E8608D40B683938C6D785627 /* credentials_provider_test.cc in Sources */,
@@ -5311,6 +5338,7 @@
 				B6FB4690208F9BB300554BA2 /* executor_test.cc in Sources */,
 				DDC782CBA37AA9B0EA373B7A /* explain_stats.pb.cc in Sources */,
 				B6D1B68520E2AB1B00B35856 /* exponential_backoff_test.cc in Sources */,
+				EC1C68ADCA37BFF885671D7A /* expression_test_util.cc in Sources */,
 				FAE5DA6ED3E1842DC21453EE /* fake_target_metadata_provider.cc in Sources */,
 				F21A3E06BBEC807FADB43AAF /* field_behavior.pb.cc in Sources */,
 				03AEB9E07A605AE1B5827548 /* field_index_test.cc in Sources */,
@@ -5363,7 +5391,7 @@
 				54C2294F1FECABAE007D065B /* log_test.cc in Sources */,
 				D156B9F19B5B29E77664FDFC /* logic_utils_test.cc in Sources */,
 				1290FA77A922B76503AE407C /* lru_garbage_collector_test.cc in Sources */,
-				618BBEA720B89AAC00B5BCE7 /* maybe_document.pb.cc in Sources */,
+				85ADFEB234EBE3D9CDFFCE12 /* maybe_document.pb.cc in Sources */,
 				C86E85101352B5CDBF5909F9 /* md5_test.cc in Sources */,
 				723BBD713478BB26CEFA5A7D /* md5_testing.cc in Sources */,
 				A0E1C7F5C7093A498F65C5CF /* memory_bundle_cache_test.cc in Sources */,
@@ -5541,6 +5569,7 @@
 				1A3D8028303B45FCBB21CAD3 /* aggregation_result.pb.cc in Sources */,
 				02EB33CC2590E1484D462912 /* annotations.pb.cc in Sources */,
 				EBFC611B1BF195D0EC710AF4 /* app_testing.mm in Sources */,
+				1792477DD2B3A1710BFD443F /* arithmetic_test.cc in Sources */,
 				FCA48FB54FC50BFDFDA672CD /* array_sorted_map_test.cc in Sources */,
 				45A5504D33D39C6F80302450 /* async_queue_libdispatch_test.mm in Sources */,
 				6F914209F46E6552B5A79570 /* async_queue_std_test.cc in Sources */,
@@ -5563,8 +5592,8 @@
 				338DFD5BCD142DF6C82A0D56 /* cc_compilation_test.cc in Sources */,
 				BACA9CDF0F2E926926B5F36F /* collection_test.cc in Sources */,
 				4C66806697D7BCA730FA3697 /* common.pb.cc in Sources */,
-				EC7A44792A5513FBB6F501EE /* comparison_test.cc in Sources */,
 				C885C84B7549C860784E4E3C /* comparison_test.cc in Sources */,
+				EC7A44792A5513FBB6F501EE /* comparison_test.cc in Sources */,
 				BDF3A6C121F2773BB3A347A7 /* counting_query_engine.cc in Sources */,
 				1F4930A8366F74288121F627 /* create_noop_connectivity_monitor.cc in Sources */,
 				7DE2560C3B4EF0512F0D538C /* credentials_provider_test.cc in Sources */,
@@ -5585,6 +5614,7 @@
 				DABB9FB61B1733F985CBF713 /* executor_test.cc in Sources */,
 				E9071BE412DC42300B936BAF /* explain_stats.pb.cc in Sources */,
 				7BCF050BA04537B0E7D44730 /* exponential_backoff_test.cc in Sources */,
+				F4DD8315F7F85F9CAB2E7206 /* expression_test_util.cc in Sources */,
 				BA1C5EAE87393D8E60F5AE6D /* fake_target_metadata_provider.cc in Sources */,
 				3A110ECBF96B6E44BA77011A /* field_behavior.pb.cc in Sources */,
 				84285C3F63D916A4786724A8 /* field_index_test.cc in Sources */,
@@ -5637,7 +5667,7 @@
 				677C833244550767B71DB1BA /* log_test.cc in Sources */,
 				6FCC64A1937E286E76C294D0 /* logic_utils_test.cc in Sources */,
 				4DF18D15AC926FB7A4888313 /* lru_garbage_collector_test.cc in Sources */,
-				12E04A12ABD5533B616D552A /* maybe_document.pb.cc in Sources */,
+				DC3351455F8753678905CF73 /* maybe_document.pb.cc in Sources */,
 				E74D6C1056DE29969B5C4C62 /* md5_test.cc in Sources */,
 				1DCDED1F94EBC7F72FDBFC98 /* md5_testing.cc in Sources */,
 				479A392EAB42453D49435D28 /* memory_bundle_cache_test.cc in Sources */,

+ 0 - 7
Firestore/Example/Podfile

@@ -126,13 +126,6 @@ if is_platform(:ios)
 
       pod 'leveldb-library'
     end
-
-    target 'Firestore_FuzzTests_iOS' do
-      inherit! :search_paths
-      platform :ios, '15.0'
-
-      pod 'LibFuzzer', :podspec => 'LibFuzzer.podspec', :inhibit_warnings => true
-    end
   end
 end
 

+ 6 - 6
Firestore/Swift/Tests/Integration/PipelineApiTests.swift

@@ -310,12 +310,12 @@ final class PipelineApiTests: FSTIntegrationTestCase {
         ]
       )
 
-    // One special Field value is conveniently exposed as constructor to help the user reference reserved field values of __name__.
-        _ = db.pipeline().collection("books")
-          .addFields([
-            DocumentId()
-            ]
-          )
+    // One special Field value is conveniently exposed as constructor to help the user reference
+    // reserved field values of __name__.
+    _ = db.pipeline().collection("books")
+      .addFields([
+        DocumentId(),
+      ])
   }
 
   func testConstant() async throws {

+ 433 - 28
Firestore/core/src/core/expressions_eval.cc

@@ -16,18 +16,227 @@
 
 #include "Firestore/core/src/core/expressions_eval.h"
 
+#include <cmath>
+#include <limits>
 #include <memory>
-#include <utility>
+#include <utility>  // For std::move
 
 #include "Firestore/core/src/api/expressions.h"
 #include "Firestore/core/src/api/stages.h"
 #include "Firestore/core/src/model/mutable_document.h"
+#include "Firestore/core/src/model/value_util.h"  // Added for value helpers
+#include "Firestore/core/src/nanopb/message.h"    // Added for MakeMessage
 #include "Firestore/core/src/remote/serializer.h"
+#include "Firestore/core/src/util/hard_assert.h"  // Added for HARD_ASSERT
+#include "absl/types/optional.h"                  // Added for absl::optional
 
 namespace firebase {
 namespace firestore {
 namespace core {
 
+namespace {
+
+// Helper functions for safe integer arithmetic with overflow detection.
+// Return nullopt on overflow or error (like division by zero).
+
+absl::optional<int64_t> SafeAdd(int64_t lhs, int64_t rhs) {
+  int64_t result;
+#if defined(__clang__) || defined(__GNUC__)
+  if (__builtin_add_overflow(lhs, rhs, &result)) {
+    return absl::nullopt;
+  }
+#else
+  // Manual check (less efficient, might miss some edge cases on weird
+  // platforms)
+  if ((rhs > 0 && lhs > std::numeric_limits<int64_t>::max() - rhs) ||
+      (rhs < 0 && lhs < std::numeric_limits<int64_t>::min() - rhs)) {
+    return absl::nullopt;
+  }
+  result = lhs + rhs;
+#endif
+  return result;
+}
+
+absl::optional<int64_t> SafeSubtract(int64_t lhs, int64_t rhs) {
+  int64_t result;
+#if defined(__clang__) || defined(__GNUC__)
+  if (__builtin_sub_overflow(lhs, rhs, &result)) {
+    return absl::nullopt;
+  }
+#else
+  // Manual check
+  if ((rhs < 0 && lhs > std::numeric_limits<int64_t>::max() + rhs) ||
+      (rhs > 0 && lhs < std::numeric_limits<int64_t>::min() + rhs)) {
+    return absl::nullopt;
+  }
+  result = lhs - rhs;
+#endif
+  return result;
+}
+
+absl::optional<int64_t> SafeMultiply(int64_t lhs, int64_t rhs) {
+  int64_t result;
+#if defined(__clang__) || defined(__GNUC__)
+  if (__builtin_mul_overflow(lhs, rhs, &result)) {
+    return absl::nullopt;
+  }
+#else
+  // Manual check (simplified, might not cover all edge cases perfectly)
+  if (lhs != 0 && rhs != 0) {
+    if (lhs > std::numeric_limits<int64_t>::max() / rhs ||
+        lhs < std::numeric_limits<int64_t>::min() / rhs) {
+      return absl::nullopt;
+    }
+  }
+  result = lhs * rhs;
+#endif
+  return result;
+}
+
+absl::optional<int64_t> SafeDivide(int64_t lhs, int64_t rhs) {
+  if (rhs == 0) {
+    return absl::nullopt;  // Division by zero
+  }
+  // Check for overflow: INT64_MIN / -1
+  if (lhs == std::numeric_limits<int64_t>::min() && rhs == -1) {
+    return absl::nullopt;
+  }
+  return lhs / rhs;
+}
+
+absl::optional<int64_t> SafeMod(int64_t lhs, int64_t rhs) {
+  if (rhs == 0) {
+    return absl::nullopt;  // Modulo by zero
+  }
+  // Check for potential overflow/UB: INT64_MIN % -1
+  if (lhs == std::numeric_limits<int64_t>::min() && rhs == -1) {
+    // The result is 0 on most platforms, but standard allows signal.
+    // Treat as error for consistency.
+    return absl::nullopt;
+  }
+  return lhs % rhs;
+}
+
+// Helper to get double value, converting integer if necessary.
+absl::optional<double> GetDoubleValue(const google_firestore_v1_Value& value) {
+  // TODO(BSON): add support for 32bit and 128bit decimal
+  if (model::IsDouble(value)) {
+    return value.double_value;
+  } else if (model::IsInteger(value)) {
+    return static_cast<double>(value.integer_value);
+  }
+  return absl::nullopt;
+}
+
+// Helper to create a Value proto from int64_t
+nanopb::Message<google_firestore_v1_Value> IntValue(int64_t val) {
+  google_firestore_v1_Value proto;
+  proto.which_value_type = google_firestore_v1_Value_integer_value_tag;
+  proto.integer_value = val;
+  return nanopb::MakeMessage(std::move(proto));
+}
+
+// Helper to create a Value proto from double
+nanopb::Message<google_firestore_v1_Value> DoubleValue(double val) {
+  google_firestore_v1_Value proto;
+  proto.which_value_type = google_firestore_v1_Value_double_value_tag;
+  proto.double_value = val;
+  return nanopb::MakeMessage(std::move(proto));
+}
+
+// Common evaluation logic for binary arithmetic operations
+// TODO(BSON): Support evaluating arithmetic on 32-bit integers and 128-bit
+// decimals
+template <typename IntOp, typename DoubleOp>
+EvaluateResult EvaluateArithmetic(const api::FunctionExpr* expr,
+                                  const api::EvaluateContext& context,
+                                  const model::PipelineInputOutput& document,
+                                  IntOp int_op,
+                                  DoubleOp double_op) {
+  HARD_ASSERT(expr, "EvaluateArithmetic was called with nullptr expression");
+  HARD_ASSERT(expr->params().size() >= 2,
+              "%s() function requires at least 2 params", expr->name());
+
+  EvaluateResult current_result =
+      expr->params()[0]->ToEvaluable()->Evaluate(context, document);
+
+  for (size_t i = 1; i < expr->params().size(); ++i) {
+    if (current_result.IsErrorOrUnset()) {
+      return EvaluateResult::NewError();
+    }
+    if (current_result.IsNull()) {
+      // Null propagates
+      return EvaluateResult::NewNull();
+    }
+
+    EvaluateResult next_operand =
+        expr->params()[i]->ToEvaluable()->Evaluate(context, document);
+
+    if (next_operand.IsErrorOrUnset()) {
+      return EvaluateResult::NewError();
+    }
+    if (next_operand.IsNull()) {
+      // Null propagates
+      return EvaluateResult::NewNull();
+    }
+
+    const google_firestore_v1_Value* left_val = current_result.value();
+    const google_firestore_v1_Value* right_val = next_operand.value();
+
+    // Type checking
+    bool left_is_num = model::IsNumber(*left_val);
+    bool right_is_num = model::IsNumber(*right_val);
+
+    if (!left_is_num || !right_is_num) {
+      return EvaluateResult::NewError();  // Type error
+    }
+
+    // NaN propagation
+    if (model::IsNaNValue(*left_val) || model::IsNaNValue(*right_val)) {
+      current_result =
+          EvaluateResult::NewValue(nanopb::MakeMessage(model::NaNValue()));
+      continue;
+    }
+
+    // Perform arithmetic
+    // TODO(BSON): Figure out the backend behavior if double arithmetic is done
+    // with a decimal128 type.
+    if (model::IsDouble(*left_val) || model::IsDouble(*right_val)) {
+      // Promote to double
+      absl::optional<double> left_double = GetDoubleValue(*left_val);
+      absl::optional<double> right_double = GetDoubleValue(*right_val);
+      // Should always succeed due to IsNumber check above
+      HARD_ASSERT(left_double.has_value() && right_double.has_value(),
+                  "Failed to extract double values");
+
+      double result_double =
+          double_op(left_double.value(), right_double.value());
+      current_result = EvaluateResult::NewValue(DoubleValue(result_double));
+
+    } else {
+      // Both are integers
+      absl::optional<int64_t> left_int = model::GetInteger(*left_val);
+      absl::optional<int64_t> right_int = model::GetInteger(*right_val);
+      // Should always succeed due to IsNumber check above
+      HARD_ASSERT(left_int.has_value() && right_int.has_value(),
+                  "Failed to extract integer values");
+
+      absl::optional<int64_t> result_int =
+          int_op(left_int.value(), right_int.value());
+
+      if (!result_int.has_value()) {
+        // Overflow or division/mod by zero
+        return EvaluateResult::NewError();
+      }
+      current_result = EvaluateResult::NewValue(IntValue(result_int.value()));
+    }
+  }
+
+  return current_result;
+}
+
+}  // anonymous namespace
+
 EvaluateResult::EvaluateResult(
     EvaluateResult::ResultType type,
     nanopb::Message<google_firestore_v1_Value> message)
@@ -82,9 +291,30 @@ std::unique_ptr<EvaluableExpr> FunctionToEvaluable(
     const api::FunctionExpr& function) {
   if (function.name() == "eq") {
     return std::make_unique<CoreEq>(function);
+  } else if (function.name() == "add") {
+    return std::make_unique<CoreAdd>(function);
+  } else if (function.name() == "subtract") {
+    return std::make_unique<CoreSubtract>(function);
+  } else if (function.name() == "multiply") {
+    return std::make_unique<CoreMultiply>(function);
+  } else if (function.name() == "divide") {
+    return std::make_unique<CoreDivide>(function);
+  } else if (function.name() == "mod") {
+    return std::make_unique<CoreMod>(function);
+  } else if (function.name() == "neq") {
+    return std::make_unique<CoreNeq>(function);
+  } else if (function.name() == "lt") {
+    return std::make_unique<CoreLt>(function);
+  } else if (function.name() == "lte") {
+    return std::make_unique<CoreLte>(function);
+  } else if (function.name() == "gt") {
+    return std::make_unique<CoreGt>(function);
+  } else if (function.name() == "gte") {
+    return std::make_unique<CoreGte>(function);
   }
+  // TODO(wuandy): Add other functions
 
-  return nullptr;
+  HARD_FAIL("Unsupported function name: %s", function.name());
 }
 
 EvaluateResult CoreField::Evaluate(
@@ -128,53 +358,228 @@ EvaluateResult CoreConstant::Evaluate(const api::EvaluateContext&,
   return EvaluateResult::NewValue(nanopb::MakeMessage(constant->to_proto()));
 }
 
-EvaluateResult CoreEq::Evaluate(
+// --- Comparison Implementations ---
+
+EvaluateResult ComparisonBase::Evaluate(
     const api::EvaluateContext& context,
     const model::PipelineInputOutput& document) const {
-  auto* api_eq = expr_.get();
-  HARD_ASSERT(api_eq->params().size() == 2,
-              "%s() function should have exactly 2 params", api_eq->name());
-
-  const auto left =
-      api_eq->params()[0]->ToEvaluable()->Evaluate(context, document);
-  switch (left.type()) {
-    case EvaluateResult::ResultType::kError:
-      return EvaluateResult::NewError();
-    case EvaluateResult::ResultType::kUnset:
-      return EvaluateResult::NewUnset();
-    default: {
-    }
+  HARD_ASSERT(expr_->params().size() == 2,
+              "%s() function requires exactly 2 params", expr_->name());
+
+  std::unique_ptr<EvaluableExpr> left_evaluable =
+      expr_->params()[0]->ToEvaluable();
+  std::unique_ptr<EvaluableExpr> right_evaluable =
+      expr_->params()[1]->ToEvaluable();
+
+  EvaluateResult left = left_evaluable->Evaluate(context, document);
+  if (left.IsErrorOrUnset()) {
+    return left;  // Propagate Error or Unset
   }
 
-  const auto right =
-      api_eq->params()[1]->ToEvaluable()->Evaluate(context, document);
-  switch (right.type()) {
-    case EvaluateResult::ResultType::kError:
-      return EvaluateResult::NewError();
-    case EvaluateResult::ResultType::kUnset:
-      return EvaluateResult::NewUnset();
-    default: {
-    }
+  EvaluateResult right = right_evaluable->Evaluate(context, document);
+  if (right.IsErrorOrUnset()) {
+    return right;  // Propagate Error or Unset
   }
 
+  // Comparisons involving Null propagate Null
   if (left.IsNull() || right.IsNull()) {
     return EvaluateResult::NewNull();
   }
 
+  // Operands are valid Values, proceed with specific comparison
+  return CompareToResult(left, right);
+}
+
+EvaluateResult CoreEq::CompareToResult(const EvaluateResult& left,
+                                       const EvaluateResult& right) const {
+  // Type mismatch always results in false for Eq
   if (model::GetTypeOrder(*left.value()) !=
       model::GetTypeOrder(*right.value())) {
     return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue()));
   }
+  // NaN == anything (including NaN) is false
   if (model::IsNaNValue(*left.value()) || model::IsNaNValue(*right.value())) {
     return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue()));
   }
 
-  // TODO(pipeline): Port strictEquals from web
-  if (model::Equals(*left.value(), *right.value())) {
+  switch (model::StrictEquals(*left.value(), *right.value())) {
+    case model::StrictEqualsResult::kEq:
+      return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue()));
+    case model::StrictEqualsResult::kNotEq:
+      return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue()));
+    case model::StrictEqualsResult::kNull:
+      return EvaluateResult::NewNull();
+  }
+}
+
+EvaluateResult CoreNeq::CompareToResult(const EvaluateResult& left,
+                                        const EvaluateResult& right) const {
+  // NaN != anything (including NaN) is true
+  if (model::IsNaNValue(*left.value()) || model::IsNaNValue(*right.value())) {
     return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue()));
-  } else {
+  }
+  // Type mismatch always results in true for Neq
+  if (model::GetTypeOrder(*left.value()) !=
+      model::GetTypeOrder(*right.value())) {
+    return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue()));
+  }
+
+  switch (model::StrictEquals(*left.value(), *right.value())) {
+    case model::StrictEqualsResult::kEq:
+      return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue()));
+    case model::StrictEqualsResult::kNotEq:
+      return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue()));
+    case model::StrictEqualsResult::kNull:
+      return EvaluateResult::NewNull();
+  }
+}
+
+EvaluateResult CoreLt::CompareToResult(const EvaluateResult& left,
+                                       const EvaluateResult& right) const {
+  // Type mismatch always results in false
+  if (model::GetTypeOrder(*left.value()) !=
+      model::GetTypeOrder(*right.value())) {
+    return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue()));
+  }
+  // NaN compared to anything is false
+  if (model::IsNaNValue(*left.value()) || model::IsNaNValue(*right.value())) {
+    return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue()));
+  }
+
+  bool result = model::Compare(*left.value(), *right.value()) ==
+                util::ComparisonResult::Ascending;
+  return EvaluateResult::NewValue(
+      nanopb::MakeMessage(result ? model::TrueValue() : model::FalseValue()));
+}
+
+EvaluateResult CoreLte::CompareToResult(const EvaluateResult& left,
+                                        const EvaluateResult& right) const {
+  // Type mismatch always results in false
+  if (model::GetTypeOrder(*left.value()) !=
+      model::GetTypeOrder(*right.value())) {
+    return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue()));
+  }
+  // NaN compared to anything is false
+  if (model::IsNaNValue(*left.value()) || model::IsNaNValue(*right.value())) {
+    return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue()));
+  }
+
+  // Check for equality first using StrictEquals
+  if (model::StrictEquals(*left.value(), *right.value()) ==
+      model::StrictEqualsResult::kEq) {
+    return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue()));
+  }
+
+  // If not equal, perform standard comparison
+  bool result = model::Compare(*left.value(), *right.value()) ==
+                util::ComparisonResult::Ascending;
+  return EvaluateResult::NewValue(
+      nanopb::MakeMessage(result ? model::TrueValue() : model::FalseValue()));
+}
+
+EvaluateResult CoreGt::CompareToResult(const EvaluateResult& left,
+                                       const EvaluateResult& right) const {
+  // Type mismatch always results in false
+  if (model::GetTypeOrder(*left.value()) !=
+      model::GetTypeOrder(*right.value())) {
+    return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue()));
+  }
+  // NaN compared to anything is false
+  if (model::IsNaNValue(*left.value()) || model::IsNaNValue(*right.value())) {
     return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue()));
   }
+
+  bool result = model::Compare(*left.value(), *right.value()) ==
+                util::ComparisonResult::Descending;
+  return EvaluateResult::NewValue(
+      nanopb::MakeMessage(result ? model::TrueValue() : model::FalseValue()));
+}
+
+EvaluateResult CoreGte::CompareToResult(const EvaluateResult& left,
+                                        const EvaluateResult& right) const {
+  // Type mismatch always results in false
+  if (model::GetTypeOrder(*left.value()) !=
+      model::GetTypeOrder(*right.value())) {
+    return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue()));
+  }
+  // NaN compared to anything is false
+  if (model::IsNaNValue(*left.value()) || model::IsNaNValue(*right.value())) {
+    return EvaluateResult::NewValue(nanopb::MakeMessage(model::FalseValue()));
+  }
+
+  // Check for equality first using StrictEquals
+  if (model::StrictEquals(*left.value(), *right.value()) ==
+      model::StrictEqualsResult::kEq) {
+    return EvaluateResult::NewValue(nanopb::MakeMessage(model::TrueValue()));
+  }
+
+  // If not equal, perform standard comparison
+  bool result = model::Compare(*left.value(), *right.value()) ==
+                util::ComparisonResult::Descending;
+  return EvaluateResult::NewValue(
+      nanopb::MakeMessage(result ? model::TrueValue() : model::FalseValue()));
+}
+
+// --- Arithmetic Implementations ---
+
+EvaluateResult CoreAdd::Evaluate(
+    const api::EvaluateContext& context,
+    const model::PipelineInputOutput& document) const {
+  return EvaluateArithmetic(
+      expr_.get(), context, document,
+      [](int64_t l, int64_t r) { return SafeAdd(l, r); },
+      [](double l, double r) { return l + r; });
+}
+
+EvaluateResult CoreSubtract::Evaluate(
+    const api::EvaluateContext& context,
+    const model::PipelineInputOutput& document) const {
+  return EvaluateArithmetic(
+      expr_.get(), context, document,
+      [](int64_t l, int64_t r) { return SafeSubtract(l, r); },
+      [](double l, double r) { return l - r; });
+}
+
+EvaluateResult CoreMultiply::Evaluate(
+    const api::EvaluateContext& context,
+    const model::PipelineInputOutput& document) const {
+  return EvaluateArithmetic(
+      expr_.get(), context, document,
+      [](int64_t l, int64_t r) { return SafeMultiply(l, r); },
+      [](double l, double r) { return l * r; });
+}
+
+EvaluateResult CoreDivide::Evaluate(
+    const api::EvaluateContext& context,
+    const model::PipelineInputOutput& document) const {
+  return EvaluateArithmetic(
+      expr_.get(), context, document,
+      // Integer division
+      [](int64_t l, int64_t r) { return SafeDivide(l, r); },
+      // Double division
+      [](double l, double r) {
+        // C++ double division handles signed zero correctly according to IEEE
+        // 754. +x / +0 -> +Inf -x / +0 -> -Inf +x / -0 -> -Inf -x / -0 -> +Inf
+        //  0 /  0 -> NaN
+        return l / r;
+      });
+}
+
+EvaluateResult CoreMod::Evaluate(
+    const api::EvaluateContext& context,
+    const model::PipelineInputOutput& document) const {
+  return EvaluateArithmetic(
+      expr_.get(), context, document,
+      // Integer modulo
+      [](int64_t l, int64_t r) { return SafeMod(l, r); },
+      // Double modulo
+      [](double l, double r) {
+        if (r == 0.0) {
+          return std::numeric_limits<double>::quiet_NaN();
+        }
+        // Use std::fmod for double modulo, matches C++ and Firestore semantics
+        return std::fmod(l, r);
+      });
 }
 
 }  // namespace core

+ 140 - 2
Firestore/core/src/core/expressions_eval.h

@@ -137,9 +137,147 @@ class CoreConstant : public EvaluableExpr {
   std::unique_ptr<api::Expr> expr_;
 };
 
-class CoreEq : public EvaluableExpr {
+/** Base class for binary comparison expressions (==, !=, <, <=, >, >=). */
+class ComparisonBase : public EvaluableExpr {
  public:
-  explicit CoreEq(const api::FunctionExpr& expr)
+  explicit ComparisonBase(const api::FunctionExpr& expr)
+      : expr_(std::make_unique<api::FunctionExpr>(expr)) {
+  }
+
+  EvaluateResult Evaluate(
+      const api::EvaluateContext& context,
+      const model::PipelineInputOutput& document) const override;
+
+ protected:
+  /**
+   * Performs the specific comparison logic after operands have been evaluated
+   * and basic checks (Error, Unset, Null) have passed.
+   */
+  virtual EvaluateResult CompareToResult(const EvaluateResult& left,
+                                         const EvaluateResult& right) const = 0;
+
+  std::unique_ptr<api::FunctionExpr> expr_;
+};
+
+class CoreEq : public ComparisonBase {
+ public:
+  explicit CoreEq(const api::FunctionExpr& expr) : ComparisonBase(expr) {
+  }
+
+ protected:
+  EvaluateResult CompareToResult(const EvaluateResult& left,
+                                 const EvaluateResult& right) const override;
+};
+
+class CoreNeq : public ComparisonBase {
+ public:
+  explicit CoreNeq(const api::FunctionExpr& expr) : ComparisonBase(expr) {
+  }
+
+ protected:
+  EvaluateResult CompareToResult(const EvaluateResult& left,
+                                 const EvaluateResult& right) const override;
+};
+
+class CoreLt : public ComparisonBase {
+ public:
+  explicit CoreLt(const api::FunctionExpr& expr) : ComparisonBase(expr) {
+  }
+
+ protected:
+  EvaluateResult CompareToResult(const EvaluateResult& left,
+                                 const EvaluateResult& right) const override;
+};
+
+class CoreLte : public ComparisonBase {
+ public:
+  explicit CoreLte(const api::FunctionExpr& expr) : ComparisonBase(expr) {
+  }
+
+ protected:
+  EvaluateResult CompareToResult(const EvaluateResult& left,
+                                 const EvaluateResult& right) const override;
+};
+
+class CoreGt : public ComparisonBase {
+ public:
+  explicit CoreGt(const api::FunctionExpr& expr) : ComparisonBase(expr) {
+  }
+
+ protected:
+  EvaluateResult CompareToResult(const EvaluateResult& left,
+                                 const EvaluateResult& right) const override;
+};
+
+class CoreGte : public ComparisonBase {
+ public:
+  explicit CoreGte(const api::FunctionExpr& expr) : ComparisonBase(expr) {
+  }
+
+ protected:
+  EvaluateResult CompareToResult(const EvaluateResult& left,
+                                 const EvaluateResult& right) const override;
+};
+
+class CoreAdd : public EvaluableExpr {
+ public:
+  explicit CoreAdd(const api::FunctionExpr& expr)
+      : expr_(std::make_unique<api::FunctionExpr>(expr)) {
+  }
+
+  EvaluateResult Evaluate(
+      const api::EvaluateContext& context,
+      const model::PipelineInputOutput& document) const override;
+
+ private:
+  std::unique_ptr<api::FunctionExpr> expr_;
+};
+
+class CoreSubtract : public EvaluableExpr {
+ public:
+  explicit CoreSubtract(const api::FunctionExpr& expr)
+      : expr_(std::make_unique<api::FunctionExpr>(expr)) {
+  }
+
+  EvaluateResult Evaluate(
+      const api::EvaluateContext& context,
+      const model::PipelineInputOutput& document) const override;
+
+ private:
+  std::unique_ptr<api::FunctionExpr> expr_;
+};
+
+class CoreMultiply : public EvaluableExpr {
+ public:
+  explicit CoreMultiply(const api::FunctionExpr& expr)
+      : expr_(std::make_unique<api::FunctionExpr>(expr)) {
+  }
+
+  EvaluateResult Evaluate(
+      const api::EvaluateContext& context,
+      const model::PipelineInputOutput& document) const override;
+
+ private:
+  std::unique_ptr<api::FunctionExpr> expr_;
+};
+
+class CoreDivide : public EvaluableExpr {
+ public:
+  explicit CoreDivide(const api::FunctionExpr& expr)
+      : expr_(std::make_unique<api::FunctionExpr>(expr)) {
+  }
+
+  EvaluateResult Evaluate(
+      const api::EvaluateContext& context,
+      const model::PipelineInputOutput& document) const override;
+
+ private:
+  std::unique_ptr<api::FunctionExpr> expr_;
+};
+
+class CoreMod : public EvaluableExpr {
+ public:
+  explicit CoreMod(const api::FunctionExpr& expr)
       : expr_(std::make_unique<api::FunctionExpr>(expr)) {
   }
 

+ 139 - 0
Firestore/core/src/model/value_util.cc

@@ -1016,6 +1016,145 @@ Message<google_firestore_v1_MapValue> DeepClone(
   return target;
 }
 
+absl::optional<int64_t> GetInteger(const google_firestore_v1_Value& value) {
+  if (value.which_value_type == google_firestore_v1_Value_integer_value_tag) {
+    return value.integer_value;
+  }
+  return absl::nullopt;
+}
+
+namespace {
+
+StrictEqualsResult StrictArrayEquals(
+    const google_firestore_v1_ArrayValue& left,
+    const google_firestore_v1_ArrayValue& right) {
+  if (left.values_count != right.values_count) {
+    return StrictEqualsResult::kNotEq;
+  }
+
+  bool found_null = false;
+  for (pb_size_t i = 0; i < left.values_count; ++i) {
+    StrictEqualsResult element_result =
+        StrictEquals(left.values[i], right.values[i]);
+    switch (element_result) {
+      case StrictEqualsResult::kNotEq:
+        return StrictEqualsResult::kNotEq;
+      case StrictEqualsResult::kNull:
+        found_null = true;
+        break;
+      case StrictEqualsResult::kEq:
+        // Continue checking other elements
+        break;
+    }
+  }
+
+  return found_null ? StrictEqualsResult::kNull : StrictEqualsResult::kEq;
+}
+
+StrictEqualsResult StrictMapEquals(const google_firestore_v1_MapValue& left,
+                                   const google_firestore_v1_MapValue& right) {
+  if (left.fields_count != right.fields_count) {
+    return StrictEqualsResult::kNotEq;
+  }
+
+  // Sort copies to compare map content regardless of original order.
+  auto left_map = DeepClone(left);
+  auto right_map = DeepClone(right);
+  SortFields(*left_map);
+  SortFields(*right_map);
+
+  bool found_null = false;
+  for (pb_size_t i = 0; i < left_map->fields_count; ++i) {
+    // Compare keys first
+    if (nanopb::MakeStringView(left_map->fields[i].key) !=
+        nanopb::MakeStringView(right_map->fields[i].key)) {
+      return StrictEqualsResult::kNotEq;
+    }
+
+    // Compare values recursively
+    StrictEqualsResult value_result =
+        StrictEquals(left_map->fields[i].value, right_map->fields[i].value);
+    switch (value_result) {
+      case StrictEqualsResult::kNotEq:
+        return StrictEqualsResult::kNotEq;
+      case StrictEqualsResult::kNull:
+        found_null = true;
+        break;
+      case StrictEqualsResult::kEq:
+        // Continue checking other fields
+        break;
+    }
+  }
+
+  return found_null ? StrictEqualsResult::kNull : StrictEqualsResult::kEq;
+}
+
+// TODO(BSON): need to add support for int32 and decimal128 later.
+StrictEqualsResult StrictNumberEquals(const google_firestore_v1_Value& left,
+                                      const google_firestore_v1_Value& right) {
+  if (left.which_value_type == google_firestore_v1_Value_integer_value_tag &&
+      right.which_value_type == google_firestore_v1_Value_integer_value_tag) {
+    // Case 1: Both are longs
+    return left.integer_value == right.integer_value
+               ? StrictEqualsResult::kEq
+               : StrictEqualsResult::kNotEq;
+  } else if (left.which_value_type ==
+                 google_firestore_v1_Value_double_value_tag &&
+             right.which_value_type ==
+                 google_firestore_v1_Value_double_value_tag) {
+    // Case 2: Both are doubles
+    // Standard double comparison handles 0.0 == -0.0 and NaN != NaN.
+    return left.double_value == right.double_value ? StrictEqualsResult::kEq
+                                                   : StrictEqualsResult::kNotEq;
+  } else {
+    // Case 3: Mixed integer and double
+    // Promote integer to double for comparison.
+    double left_double =
+        (left.which_value_type == google_firestore_v1_Value_integer_value_tag)
+            ? static_cast<double>(left.integer_value)
+            : left.double_value;
+    double right_double =
+        (right.which_value_type == google_firestore_v1_Value_integer_value_tag)
+            ? static_cast<double>(right.integer_value)
+            : right.double_value;
+    return left_double == right_double ? StrictEqualsResult::kEq
+                                       : StrictEqualsResult::kNotEq;
+  }
+}
+
+}  // namespace
+
+StrictEqualsResult StrictEquals(const google_firestore_v1_Value& left,
+                                const google_firestore_v1_Value& right) {
+  if (IsNullValue(left) || IsNullValue(right)) {
+    return StrictEqualsResult::kNull;
+  }
+
+  TypeOrder left_type = GetTypeOrder(left);
+  TypeOrder right_type = GetTypeOrder(right);
+  if (left_type != right_type) {
+    return StrictEqualsResult::kNotEq;
+  }
+
+  switch (left_type) {
+    case TypeOrder::kNumber:
+      return StrictNumberEquals(left, right);
+    case TypeOrder::kArray:
+      return StrictArrayEquals(left.array_value, right.array_value);
+    case TypeOrder::kVector:
+    case TypeOrder::kMap:
+      // Note: MaxValue is also a map, but should be handled by TypeOrder check
+      // if compared against a non-MaxValue. MaxValue == MaxValue is handled
+      // by the Equals call below. Vector equality is map equality.
+      return StrictMapEquals(left.map_value, right.map_value);
+    default:
+      // For all other types (Null, Boolean, Timestamp, String, Blob,
+      // Ref, GeoPoint, MaxValue), the standard Equals function works.
+      return Equals(left, right) ? StrictEqualsResult::kEq
+                                 : StrictEqualsResult::kNotEq;
+  }
+}
+
 }  // namespace model
 }  // namespace firestore
 }  // namespace firebase

+ 18 - 0
Firestore/core/src/model/value_util.h

@@ -77,6 +77,9 @@ enum class TypeOrder {
   kMaxValue = 12
 };
 
+/** Result type for StrictEquals comparison. */
+enum class StrictEqualsResult { kEq, kNotEq, kNull };
+
 /** Returns the backend's type order of the given Value type. */
 TypeOrder GetTypeOrder(const google_firestore_v1_Value& value);
 
@@ -103,6 +106,15 @@ bool Equals(const google_firestore_v1_Value& left,
 bool Equals(const google_firestore_v1_ArrayValue& left,
             const google_firestore_v1_ArrayValue& right);
 
+/**
+ * Performs a strict equality comparison used in Pipeline expressions
+ * evaluations. The main difference to Equals is its handling of null
+ * propagation, and it uses direct double value comparison (as opposed to Equals
+ * which use bits comparison).
+ */
+StrictEqualsResult StrictEquals(const google_firestore_v1_Value& left,
+                                const google_firestore_v1_Value& right);
+
 /**
  * Generates the canonical ID for the provided field value (as used in Target
  * serialization).
@@ -277,6 +289,12 @@ inline bool IsMap(const absl::optional<google_firestore_v1_Value>& value) {
          value->which_value_type == google_firestore_v1_Value_map_value_tag;
 }
 
+/**
+ * Extracts the integer value if the input is an integer type.
+ * Returns nullopt otherwise.
+ */
+absl::optional<int64_t> GetInteger(const google_firestore_v1_Value& value);
+
 }  // namespace model
 
 inline bool operator==(const google_firestore_v1_Value& lhs,

+ 832 - 0
Firestore/core/test/unit/core/expressions/arithmetic_test.cc

@@ -0,0 +1,832 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/src/core/expressions_eval.h"
+
+#include <initializer_list>
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "Firestore/core/test/unit/testutil/expression_test_util.h"
+#include "Firestore/core/test/unit/testutil/testutil.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace core {
+
+using api::Expr;
+using model::MutableDocument;  // Used as PipelineInputOutput alias
+using testing::_;
+using testutil::AddExpr;
+using testutil::DivideExpr;
+using testutil::EvaluateExpr;
+using testutil::ModExpr;
+using testutil::MultiplyExpr;
+using testutil::Returns;
+using testutil::ReturnsError;
+using testutil::SharedConstant;
+using testutil::SubtractExpr;
+using testutil::Value;
+
+// Base fixture for common setup (if needed later)
+class ArithmeticExpressionsTest : public ::testing::Test {};
+
+// Fixture for Add function tests
+class AddFunctionTest : public ArithmeticExpressionsTest {};
+
+// Fixture for Subtract function tests
+class SubtractFunctionTest : public ArithmeticExpressionsTest {};
+
+// Fixture for Multiply function tests
+class MultiplyFunctionTest : public ArithmeticExpressionsTest {};
+
+// Fixture for Divide function tests
+class DivideFunctionTest : public ArithmeticExpressionsTest {};
+
+// Fixture for Mod function tests
+class ModFunctionTest : public ArithmeticExpressionsTest {};
+
+// --- Add Tests ---
+
+TEST_F(AddFunctionTest, BasicNumerics) {
+  EXPECT_THAT(
+      EvaluateExpr(*AddExpr({SharedConstant(1LL), SharedConstant(2LL)})),
+      Returns(Value(3LL)));
+  EXPECT_THAT(
+      EvaluateExpr(*AddExpr({SharedConstant(1LL), SharedConstant(2.5)})),
+      Returns(Value(3.5)));
+  EXPECT_THAT(
+      EvaluateExpr(*AddExpr({SharedConstant(1.0), SharedConstant(2LL)})),
+      Returns(Value(3.0)));
+  EXPECT_THAT(
+      EvaluateExpr(*AddExpr({SharedConstant(1.0), SharedConstant(2.0)})),
+      Returns(Value(3.0)));
+}
+
+TEST_F(AddFunctionTest, BasicNonNumerics) {
+  EXPECT_THAT(
+      EvaluateExpr(*AddExpr({SharedConstant(1LL), SharedConstant("1")})),
+      ReturnsError());
+  EXPECT_THAT(
+      EvaluateExpr(*AddExpr({SharedConstant("1"), SharedConstant(1.0)})),
+      ReturnsError());
+  EXPECT_THAT(
+      EvaluateExpr(*AddExpr({SharedConstant("1"), SharedConstant("1")})),
+      ReturnsError());
+}
+
+TEST_F(AddFunctionTest, DoubleLongAdditionOverflow) {
+  // Note: C++ double can represent Long.MAX_VALUE + 1.0 exactly, unlike some JS
+  // representations.
+  EXPECT_THAT(EvaluateExpr(*AddExpr({SharedConstant(9223372036854775807LL),
+                                     SharedConstant(1.0)})),
+              Returns(Value(9.223372036854776e+18)));
+  EXPECT_THAT(EvaluateExpr(*AddExpr({SharedConstant(9.223372036854776e+18),
+                                     SharedConstant(100LL)})),
+              Returns(Value(9.223372036854776e+18 + 100.0)));
+}
+
+TEST_F(AddFunctionTest, DoubleAdditionOverflow) {
+  EXPECT_THAT(EvaluateExpr(*AddExpr(
+                  {SharedConstant(std::numeric_limits<double>::max()),
+                   SharedConstant(std::numeric_limits<double>::max())})),
+              Returns(Value(std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(EvaluateExpr(*AddExpr(
+                  {SharedConstant(-std::numeric_limits<double>::max()),
+                   SharedConstant(-std::numeric_limits<double>::max())})),
+              Returns(Value(-std::numeric_limits<double>::infinity())));
+}
+
+TEST_F(AddFunctionTest, SumPosAndNegInfinityReturnNaN) {
+  EXPECT_THAT(EvaluateExpr(*AddExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(-std::numeric_limits<double>::infinity())})),
+              Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+}
+
+TEST_F(AddFunctionTest, LongAdditionOverflow) {
+  EXPECT_THAT(EvaluateExpr(
+                  *AddExpr({SharedConstant(std::numeric_limits<int64_t>::max()),
+                            SharedConstant(1LL)})),
+              ReturnsError());  // Expect error due to overflow
+  EXPECT_THAT(EvaluateExpr(
+                  *AddExpr({SharedConstant(std::numeric_limits<int64_t>::min()),
+                            SharedConstant(-1LL)})),
+              ReturnsError());  // Expect error due to overflow
+  EXPECT_THAT(EvaluateExpr(*AddExpr(
+                  {SharedConstant(1LL),
+                   SharedConstant(std::numeric_limits<int64_t>::max())})),
+              ReturnsError());  // Expect error due to overflow
+}
+
+TEST_F(AddFunctionTest, NanNumberReturnNaN) {
+  double nan_val = std::numeric_limits<double>::quiet_NaN();
+  EXPECT_THAT(
+      EvaluateExpr(*AddExpr({SharedConstant(1LL), SharedConstant(nan_val)})),
+      Returns(Value(nan_val)));
+  EXPECT_THAT(
+      EvaluateExpr(*AddExpr({SharedConstant(1.0), SharedConstant(nan_val)})),
+      Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*AddExpr({SharedConstant(9007199254740991LL),
+                                     SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*AddExpr({SharedConstant(-9007199254740991LL),
+                                     SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(
+      EvaluateExpr(*AddExpr({SharedConstant(std::numeric_limits<double>::max()),
+                             SharedConstant(nan_val)})),
+      Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*AddExpr(
+                  {SharedConstant(std::numeric_limits<double>::lowest()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*AddExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*AddExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+}
+
+TEST_F(AddFunctionTest, NanNotNumberTypeReturnError) {
+  EXPECT_THAT(EvaluateExpr(*AddExpr(
+                  {SharedConstant(std::numeric_limits<double>::quiet_NaN()),
+                   SharedConstant("hello world")})),
+              ReturnsError());
+}
+
+TEST_F(AddFunctionTest, MultiArgument) {
+  // EvaluateExpr handles single expression, so nest calls for multi-arg
+  auto add12 = AddExpr({SharedConstant(1LL), SharedConstant(2LL)});
+  EXPECT_THAT(EvaluateExpr(*AddExpr({add12, SharedConstant(3LL)})),
+              Returns(Value(6LL)));
+
+  auto add10_2 = AddExpr({SharedConstant(1.0), SharedConstant(2LL)});
+  EXPECT_THAT(EvaluateExpr(*AddExpr({add10_2, SharedConstant(3LL)})),
+              Returns(Value(6.0)));
+}
+
+// --- Subtract Tests ---
+
+TEST_F(SubtractFunctionTest, BasicNumerics) {
+  EXPECT_THAT(
+      EvaluateExpr(*SubtractExpr({SharedConstant(1LL), SharedConstant(2LL)})),
+      Returns(Value(-1LL)));
+  EXPECT_THAT(
+      EvaluateExpr(*SubtractExpr({SharedConstant(1LL), SharedConstant(2.5)})),
+      Returns(Value(-1.5)));
+  EXPECT_THAT(
+      EvaluateExpr(*SubtractExpr({SharedConstant(1.0), SharedConstant(2LL)})),
+      Returns(Value(-1.0)));
+  EXPECT_THAT(
+      EvaluateExpr(*SubtractExpr({SharedConstant(1.0), SharedConstant(2.0)})),
+      Returns(Value(-1.0)));
+}
+
+TEST_F(SubtractFunctionTest, BasicNonNumerics) {
+  EXPECT_THAT(
+      EvaluateExpr(*SubtractExpr({SharedConstant(1LL), SharedConstant("1")})),
+      ReturnsError());
+  EXPECT_THAT(
+      EvaluateExpr(*SubtractExpr({SharedConstant("1"), SharedConstant(1.0)})),
+      ReturnsError());
+  EXPECT_THAT(
+      EvaluateExpr(*SubtractExpr({SharedConstant("1"), SharedConstant("1")})),
+      ReturnsError());
+}
+
+TEST_F(SubtractFunctionTest, DoubleSubtractionOverflow) {
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(-std::numeric_limits<double>::max()),
+                   SharedConstant(std::numeric_limits<double>::max())})),
+              Returns(Value(-std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(std::numeric_limits<double>::max()),
+                   SharedConstant(-std::numeric_limits<double>::max())})),
+              Returns(Value(std::numeric_limits<double>::infinity())));
+}
+
+TEST_F(SubtractFunctionTest, LongSubtractionOverflow) {
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(std::numeric_limits<int64_t>::min()),
+                   SharedConstant(1LL)})),
+              ReturnsError());
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(std::numeric_limits<int64_t>::max()),
+                   SharedConstant(-1LL)})),
+              ReturnsError());
+}
+
+TEST_F(SubtractFunctionTest, NanNumberReturnNaN) {
+  double nan_val = std::numeric_limits<double>::quiet_NaN();
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(1LL), SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(1.0), SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr({SharedConstant(9007199254740991LL),
+                                          SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr({SharedConstant(-9007199254740991LL),
+                                          SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(std::numeric_limits<double>::max()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(std::numeric_limits<double>::lowest()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+}
+
+TEST_F(SubtractFunctionTest, NanNotNumberTypeReturnError) {
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(std::numeric_limits<double>::quiet_NaN()),
+                   SharedConstant("hello world")})),
+              ReturnsError());
+}
+
+TEST_F(SubtractFunctionTest, PositiveInfinity) {
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(1LL)})),
+              Returns(Value(std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(1LL),
+                   SharedConstant(std::numeric_limits<double>::infinity())})),
+              Returns(Value(-std::numeric_limits<double>::infinity())));
+}
+
+TEST_F(SubtractFunctionTest, NegativeInfinity) {
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(1LL)})),
+              Returns(Value(-std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(1LL),
+                   SharedConstant(-std::numeric_limits<double>::infinity())})),
+              Returns(Value(std::numeric_limits<double>::infinity())));
+}
+
+TEST_F(SubtractFunctionTest, PositiveInfinityNegativeInfinity) {
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(-std::numeric_limits<double>::infinity())})),
+              Returns(Value(std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(EvaluateExpr(*SubtractExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(std::numeric_limits<double>::infinity())})),
+              Returns(Value(-std::numeric_limits<double>::infinity())));
+}
+
+// --- Multiply Tests ---
+
+TEST_F(MultiplyFunctionTest, BasicNumerics) {
+  EXPECT_THAT(
+      EvaluateExpr(*MultiplyExpr({SharedConstant(1LL), SharedConstant(2LL)})),
+      Returns(Value(2LL)));
+  EXPECT_THAT(
+      EvaluateExpr(*MultiplyExpr({SharedConstant(3LL), SharedConstant(2.5)})),
+      Returns(Value(7.5)));
+  EXPECT_THAT(
+      EvaluateExpr(*MultiplyExpr({SharedConstant(1.0), SharedConstant(2LL)})),
+      Returns(Value(2.0)));
+  EXPECT_THAT(
+      EvaluateExpr(*MultiplyExpr({SharedConstant(1.32), SharedConstant(2.0)})),
+      Returns(Value(2.64)));
+}
+
+TEST_F(MultiplyFunctionTest, BasicNonNumerics) {
+  EXPECT_THAT(
+      EvaluateExpr(*MultiplyExpr({SharedConstant(1LL), SharedConstant("1")})),
+      ReturnsError());
+  EXPECT_THAT(
+      EvaluateExpr(*MultiplyExpr({SharedConstant("1"), SharedConstant(1.0)})),
+      ReturnsError());
+  EXPECT_THAT(
+      EvaluateExpr(*MultiplyExpr({SharedConstant("1"), SharedConstant("1")})),
+      ReturnsError());
+}
+
+TEST_F(MultiplyFunctionTest, DoubleLongMultiplicationOverflow) {
+  // C++ double handles this fine
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr({SharedConstant(9223372036854775807LL),
+                                          SharedConstant(100.0)})),
+              Returns(Value(9.223372036854776e+20)));  // Approx
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr({SharedConstant(9223372036854775807LL),
+                                          SharedConstant(100LL)})),
+              ReturnsError());  // Integer overflow
+}
+
+TEST_F(MultiplyFunctionTest, DoubleMultiplicationOverflow) {
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(std::numeric_limits<double>::max()),
+                   SharedConstant(std::numeric_limits<double>::max())})),
+              Returns(Value(std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(-std::numeric_limits<double>::max()),
+                   SharedConstant(std::numeric_limits<double>::max())})),
+              Returns(Value(-std::numeric_limits<double>::infinity())));
+}
+
+TEST_F(MultiplyFunctionTest, LongMultiplicationOverflow) {
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(std::numeric_limits<int64_t>::max()),
+                   SharedConstant(10LL)})),
+              ReturnsError());
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(std::numeric_limits<int64_t>::min()),
+                   SharedConstant(10LL)})),
+              ReturnsError());
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(-10LL),
+                   SharedConstant(std::numeric_limits<int64_t>::max())})),
+              ReturnsError());
+  // Note: min * -10 overflows
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(-10LL),
+                   SharedConstant(std::numeric_limits<int64_t>::min())})),
+              ReturnsError());
+}
+
+TEST_F(MultiplyFunctionTest, NanNumberReturnNaN) {
+  double nan_val = std::numeric_limits<double>::quiet_NaN();
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(1LL), SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(1.0), SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr({SharedConstant(9007199254740991LL),
+                                          SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr({SharedConstant(-9007199254740991LL),
+                                          SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(std::numeric_limits<double>::max()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(std::numeric_limits<double>::lowest()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+}
+
+TEST_F(MultiplyFunctionTest, NanNotNumberTypeReturnError) {
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(std::numeric_limits<double>::quiet_NaN()),
+                   SharedConstant("hello world")})),
+              ReturnsError());
+}
+
+TEST_F(MultiplyFunctionTest, PositiveInfinity) {
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(1LL)})),
+              Returns(Value(std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(1LL),
+                   SharedConstant(std::numeric_limits<double>::infinity())})),
+              Returns(Value(std::numeric_limits<double>::infinity())));
+}
+
+TEST_F(MultiplyFunctionTest, NegativeInfinity) {
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(1LL)})),
+              Returns(Value(-std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(1LL),
+                   SharedConstant(-std::numeric_limits<double>::infinity())})),
+              Returns(Value(-std::numeric_limits<double>::infinity())));
+}
+
+TEST_F(MultiplyFunctionTest,
+       PositiveInfinityNegativeInfinityReturnsNegativeInfinity) {
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(-std::numeric_limits<double>::infinity())})),
+              Returns(Value(-std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(std::numeric_limits<double>::infinity())})),
+              Returns(Value(-std::numeric_limits<double>::infinity())));
+}
+
+TEST_F(MultiplyFunctionTest, MultiArgument) {
+  auto mult12 = MultiplyExpr({SharedConstant(1LL), SharedConstant(2LL)});
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr({mult12, SharedConstant(3LL)})),
+              Returns(Value(6LL)));
+
+  auto mult23 = MultiplyExpr({SharedConstant(2LL), SharedConstant(3LL)});
+  EXPECT_THAT(EvaluateExpr(*MultiplyExpr({SharedConstant(1.0), mult23})),
+              Returns(Value(6.0)));
+}
+
+// --- Divide Tests ---
+
+TEST_F(DivideFunctionTest, BasicNumerics) {
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(10LL), SharedConstant(2LL)})),
+      Returns(Value(5LL)));
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(10LL), SharedConstant(2.0)})),
+      Returns(Value(5.0)));
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(10.0), SharedConstant(3LL)})),
+      Returns(Value(10.0 / 3.0)));
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(10.0), SharedConstant(7.0)})),
+      Returns(Value(10.0 / 7.0)));
+}
+
+TEST_F(DivideFunctionTest, BasicNonNumerics) {
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(1LL), SharedConstant("1")})),
+      ReturnsError());
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant("1"), SharedConstant(1.0)})),
+      ReturnsError());
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant("1"), SharedConstant("1")})),
+      ReturnsError());
+}
+
+TEST_F(DivideFunctionTest, LongDivision) {
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(10LL), SharedConstant(3LL)})),
+      Returns(Value(3LL)));  // Integer division
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(-10LL), SharedConstant(3LL)})),
+      Returns(Value(-3LL)));  // Integer division
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(10LL), SharedConstant(-3LL)})),
+      Returns(Value(-3LL)));  // Integer division
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(-10LL), SharedConstant(-3LL)})),
+      Returns(Value(3LL)));  // Integer division
+}
+
+TEST_F(DivideFunctionTest, DoubleDivisionOverflow) {
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(std::numeric_limits<double>::max()),
+                   SharedConstant(0.5)})),  // Multiplying by 2 essentially
+              Returns(Value(std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(-std::numeric_limits<double>::max()),
+                   SharedConstant(0.5)})),
+              Returns(Value(-std::numeric_limits<double>::infinity())));
+}
+
+TEST_F(DivideFunctionTest, ByZero) {
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(1LL), SharedConstant(0LL)})),
+      ReturnsError());  // Integer division by zero is error
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(1.1), SharedConstant(0.0)})),
+      Returns(Value(std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(1.1), SharedConstant(-0.0)})),
+      Returns(Value(-std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(0.0), SharedConstant(0.0)})),
+      Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+}
+
+TEST_F(DivideFunctionTest, NanNumberReturnNaN) {
+  double nan_val = std::numeric_limits<double>::quiet_NaN();
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(1LL), SharedConstant(nan_val)})),
+      Returns(Value(nan_val)));
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(nan_val), SharedConstant(1LL)})),
+      Returns(Value(nan_val)));
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(1.0), SharedConstant(nan_val)})),
+      Returns(Value(nan_val)));
+  EXPECT_THAT(
+      EvaluateExpr(*DivideExpr({SharedConstant(nan_val), SharedConstant(1.0)})),
+      Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(nan_val), SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(nan_val),
+                   SharedConstant(-std::numeric_limits<double>::infinity())})),
+              Returns(Value(nan_val)));
+}
+
+TEST_F(DivideFunctionTest, NanNotNumberTypeReturnError) {
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(std::numeric_limits<double>::quiet_NaN()),
+                   SharedConstant("hello world")})),
+              ReturnsError());
+}
+
+TEST_F(DivideFunctionTest, PositiveInfinity) {
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(1LL)})),
+              Returns(Value(std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(1LL),
+                   SharedConstant(std::numeric_limits<double>::infinity())})),
+              Returns(Value(0.0)));
+}
+
+TEST_F(DivideFunctionTest, NegativeInfinity) {
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(1LL)})),
+              Returns(Value(-std::numeric_limits<double>::infinity())));
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(1LL),
+                   SharedConstant(-std::numeric_limits<double>::infinity())})),
+              Returns(Value(-0.0)));  // Note: -0.0
+}
+
+TEST_F(DivideFunctionTest, PositiveInfinityNegativeInfinityReturnsNan) {
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(-std::numeric_limits<double>::infinity())})),
+              Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+  EXPECT_THAT(EvaluateExpr(*DivideExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(std::numeric_limits<double>::infinity())})),
+              Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+}
+
+// --- Mod Tests ---
+
+TEST_F(ModFunctionTest, DivisorZeroThrowsError) {
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(42LL), SharedConstant(0LL)})),
+      ReturnsError());
+  // Note: C++ doesn't distinguish -0LL from 0LL
+  // EXPECT_TRUE(AssertResultEquals(
+  //     EvaluateExpr(*ModExpr({SharedConstant(42LL), SharedConstant(-0LL)})),
+  //     EvaluateResult::NewError()));
+
+  // Double modulo by zero returns NaN in our implementation (matching JS %)
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(42.0), SharedConstant(0.0)})),
+      Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(42.0), SharedConstant(-0.0)})),
+      Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+}
+
+TEST_F(ModFunctionTest, DividendZeroReturnsZero) {
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(0LL), SharedConstant(42LL)})),
+      Returns(Value(0LL)));
+  // Note: C++ doesn't distinguish -0LL from 0LL
+  // EXPECT_THAT(
+  //     EvaluateExpr(*ModExpr({SharedConstant(-0LL), SharedConstant(42LL)})),
+  //     Returns(Value(0LL)));
+
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(0.0), SharedConstant(42.0)})),
+      Returns(Value(0.0)));
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(-0.0), SharedConstant(42.0)})),
+      Returns(Value(-0.0)));
+}
+
+TEST_F(ModFunctionTest, LongPositivePositive) {
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(10LL), SharedConstant(3LL)})),
+      Returns(Value(1LL)));
+}
+
+TEST_F(ModFunctionTest, LongNegativeNegative) {
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(-10LL), SharedConstant(-3LL)})),
+      Returns(Value(-1LL)));  // C++ % behavior
+}
+
+TEST_F(ModFunctionTest, LongPositiveNegative) {
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(10LL), SharedConstant(-3LL)})),
+      Returns(Value(1LL)));  // C++ % behavior
+}
+
+TEST_F(ModFunctionTest, LongNegativePositive) {
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(-10LL), SharedConstant(3LL)})),
+      Returns(Value(-1LL)));  // C++ % behavior
+}
+
+TEST_F(ModFunctionTest, DoublePositivePositive) {
+  auto result =
+      EvaluateExpr(*ModExpr({SharedConstant(10.5), SharedConstant(3.0)}));
+  EXPECT_EQ(result.type(), EvaluateResult::ResultType::kDouble);
+  EXPECT_NEAR(result.value()->double_value, 1.5, 1e-9);
+}
+
+TEST_F(ModFunctionTest, DoubleNegativeNegative) {
+  auto result =
+      EvaluateExpr(*ModExpr({SharedConstant(-7.3), SharedConstant(-1.8)}));
+  EXPECT_EQ(result.type(), EvaluateResult::ResultType::kDouble);
+  EXPECT_NEAR(result.value()->double_value, -0.1, 1e-9);  // std::fmod behavior
+}
+
+TEST_F(ModFunctionTest, DoublePositiveNegative) {
+  auto result =
+      EvaluateExpr(*ModExpr({SharedConstant(9.8), SharedConstant(-2.5)}));
+  EXPECT_EQ(result.type(), EvaluateResult::ResultType::kDouble);
+  EXPECT_NEAR(result.value()->double_value, 2.3, 1e-9);  // std::fmod behavior
+}
+
+TEST_F(ModFunctionTest, DoubleNegativePositive) {
+  auto result =
+      EvaluateExpr(*ModExpr({SharedConstant(-7.5), SharedConstant(2.3)}));
+  EXPECT_EQ(result.type(), EvaluateResult::ResultType::kDouble);
+  EXPECT_NEAR(result.value()->double_value, -0.6, 1e-9);  // std::fmod behavior
+}
+
+TEST_F(ModFunctionTest, LongPerfectlyDivisible) {
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(10LL), SharedConstant(5LL)})),
+      Returns(Value(0LL)));
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(-10LL), SharedConstant(5LL)})),
+      Returns(Value(0LL)));
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(10LL), SharedConstant(-5LL)})),
+      Returns(Value(0LL)));
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(-10LL), SharedConstant(-5LL)})),
+      Returns(Value(0LL)));
+}
+
+TEST_F(ModFunctionTest, DoublePerfectlyDivisible) {
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(10.0), SharedConstant(2.5)})),
+      Returns(Value(0.0)));
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(10.0), SharedConstant(-2.5)})),
+      Returns(Value(0.0)));
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(-10.0), SharedConstant(2.5)})),
+      Returns(Value(-0.0)));
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(-10.0), SharedConstant(-2.5)})),
+      Returns(Value(-0.0)));
+}
+
+TEST_F(ModFunctionTest, NonNumericsReturnError) {
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(10LL), SharedConstant("1")})),
+      ReturnsError());
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant("1"), SharedConstant(10LL)})),
+      ReturnsError());
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant("1"), SharedConstant("1")})),
+      ReturnsError());
+}
+
+TEST_F(ModFunctionTest, NanNumberReturnNaN) {
+  double nan_val = std::numeric_limits<double>::quiet_NaN();
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(1LL), SharedConstant(nan_val)})),
+      Returns(Value(nan_val)));
+  EXPECT_THAT(
+      EvaluateExpr(*ModExpr({SharedConstant(1.0), SharedConstant(nan_val)})),
+      Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(nan_val)})),
+              Returns(Value(nan_val)));
+}
+
+TEST_F(ModFunctionTest, NanNotNumberTypeReturnError) {
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(std::numeric_limits<double>::quiet_NaN()),
+                   SharedConstant("hello world")})),
+              ReturnsError());
+}
+
+TEST_F(ModFunctionTest, NumberPosInfinityReturnSelf) {
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(1LL),
+                   SharedConstant(std::numeric_limits<double>::infinity())})),
+              Returns(Value(1.0)));  // fmod(1, inf) -> 1
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(42.123),
+                   SharedConstant(std::numeric_limits<double>::infinity())})),
+              Returns(Value(42.123)));
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(-99.9),
+                   SharedConstant(std::numeric_limits<double>::infinity())})),
+              Returns(Value(-99.9)));
+}
+
+TEST_F(ModFunctionTest, PosInfinityNumberReturnNaN) {
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(1LL)})),
+              Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(42.123)})),
+              Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(-99.9)})),
+              Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+}
+
+TEST_F(ModFunctionTest, NumberNegInfinityReturnSelf) {
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(1LL),
+                   SharedConstant(-std::numeric_limits<double>::infinity())})),
+              Returns(Value(1.0)));  // fmod(1, -inf) -> 1
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(42.123),
+                   SharedConstant(-std::numeric_limits<double>::infinity())})),
+              Returns(Value(42.123)));
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(-99.9),
+                   SharedConstant(-std::numeric_limits<double>::infinity())})),
+              Returns(Value(-99.9)));
+}
+
+TEST_F(ModFunctionTest, NegInfinityNumberReturnNaN) {
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(1LL)})),
+              Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(42.123)})),
+              Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(-std::numeric_limits<double>::infinity()),
+                   SharedConstant(-99.9)})),
+              Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+}
+
+TEST_F(ModFunctionTest, PosAndNegInfinityReturnNaN) {
+  EXPECT_THAT(EvaluateExpr(*ModExpr(
+                  {SharedConstant(std::numeric_limits<double>::infinity()),
+                   SharedConstant(-std::numeric_limits<double>::infinity())})),
+              Returns(Value(std::numeric_limits<double>::quiet_NaN())));
+}
+
+}  // namespace core
+}  // namespace firestore
+}  // namespace firebase

+ 896 - 35
Firestore/core/test/unit/core/expressions/comparison_test.cc

@@ -14,60 +14,921 @@
  * limitations under the License.
  */
 
+#include "Firestore/core/src/core/expressions_eval.h"  // For EvaluateResult, CoreEq etc.
+
+#include <initializer_list>
+#include <limits>
 #include <memory>
+#include <string>
 #include <utility>
+#include <vector>
 
-#include "Firestore/core/src/api/expressions.h"
-#include "Firestore/core/src/api/stages.h"
-#include "Firestore/core/src/core/expressions_eval.h"
-#include "Firestore/core/src/model/database_id.h"
-#include "Firestore/core/src/model/value_util.h"
-#include "Firestore/core/src/nanopb/message.h"
-#include "Firestore/core/src/remote/serializer.h"
-#include "Firestore/core/test/unit/testutil/testutil.h"
-#include "google/firestore/v1/document.nanopb.h"
-
+#include "Firestore/core/src/api/expressions.h"  // Include for api::Constant, api::Field
+#include "Firestore/core/src/model/database_id.h"   // For DatabaseId
+#include "Firestore/core/src/model/document_key.h"  // For DocumentKey
+#include "Firestore/core/src/model/value_util.h"  // For value constants like NaNValue, TypeOrder, NullValue, CanonicalId, Equals
+#include "Firestore/core/test/unit/testutil/expression_test_util.h"  // For EvaluateExpr, EqExpr, ComparisonValueTestData, RefConstant etc.
+#include "Firestore/core/test/unit/testutil/testutil.h"  // For test helpers like Value, Array, Map, BlobValue, Doc
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 namespace firebase {
 namespace firestore {
+namespace core {
+
+using api::Expr;
+using model::DatabaseId;
+using model::DocumentKey;
+using model::MutableDocument;  // Used as PipelineInputOutput alias
+using testing::_;
+// Explicitly qualify testutil helpers to avoid ambiguity
+using testutil::ComparisonValueTestData;
+using testutil::EqExpr;
+using testutil::EvaluateExpr;
+using testutil::GteExpr;
+using testutil::GtExpr;
+using testutil::LteExpr;
+using testutil::LtExpr;
+using testutil::NeqExpr;
+using testutil::RefConstant;
+using testutil::Returns;
+using testutil::ReturnsError;
+using testutil::ReturnsNull;
+using testutil::ReturnsUnset;
+using testutil::SharedConstant;
+
+// Base fixture for common setup
+class ComparisonExpressionsTest : public ::testing::Test {
+ protected:
+  // Helper moved to expression_test_util.h
+};
 
-namespace {
+// Fixture for Eq function tests
+class EqFunctionTest : public ComparisonExpressionsTest {};
 
-template <typename T, typename Q>
-api::FunctionExpr eq(T lhs, Q rhs) {
-  return api::FunctionExpr(
-      "eq", {std::make_shared<T>(lhs), std::make_shared<Q>(rhs)});
+// Helper to get canonical ID for logging, handling potential non-constant exprs
+std::string ExprId(const std::shared_ptr<Expr>& expr) {
+  if (auto constant = std::dynamic_pointer_cast<const api::Constant>(expr)) {
+    // Try accessing the underlying proto message via proto()
+    return model::CanonicalId(constant->to_proto());
+  } else if (auto field = std::dynamic_pointer_cast<const api::Field>(expr)) {
+    return "Field(" + field->field_path().CanonicalString() + ")";
+  }
+  return "<unknown_expr_type>";
 }
 
-api::Constant constant(int value) {
-  google_firestore_v1_Value result;
-  result.which_value_type = google_firestore_v1_Value_integer_value_tag;
-  result.integer_value = value;
-  return api::Constant(nanopb::MakeSharedMessage(std::move(result)));
+TEST_F(EqFunctionTest, EquivalentValuesReturnTrue) {
+  for (const auto& pair : ComparisonValueTestData::EquivalentValues()) {
+    EXPECT_THAT(EvaluateExpr(*EqExpr({pair.first, pair.second})),
+                Returns(testutil::Value(true)))
+        << "eq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
 }
 
-remote::Serializer serializer(model::DatabaseId("test-project"));
+TEST_F(EqFunctionTest, LessThanValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::LessThanValues()) {
+    EXPECT_THAT(EvaluateExpr(*EqExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "eq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(EqFunctionTest, GreaterThanValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::GreaterThanValues()) {
+    EXPECT_THAT(EvaluateExpr(*EqExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "eq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
 
-api::EvaluateContext NewContext() {
-  return api::EvaluateContext{&serializer};
+TEST_F(EqFunctionTest, MixedTypeValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::MixedTypeValues()) {
+    EXPECT_THAT(EvaluateExpr(*EqExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "eq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
 }
 
-}  // namespace
+// --- Specific Eq Tests (Null, NaN, Missing, Error) ---
 
-namespace core {
+// Fixture for Neq function tests
+class NeqFunctionTest : public ComparisonExpressionsTest {};
+
+// Fixture for Lt function tests
+class LtFunctionTest : public ComparisonExpressionsTest {};
+
+// Fixture for Lte function tests
+class LteFunctionTest : public ComparisonExpressionsTest {};
+
+// Fixture for Gt function tests
+class GtFunctionTest : public ComparisonExpressionsTest {};
+
+// Fixture for Gte function tests
+class GteFunctionTest : public ComparisonExpressionsTest {};
+
+// --- Eq (==) Tests ---
+
+TEST_F(EqFunctionTest, NullEqualsNullReturnsNull) {
+  EXPECT_THAT(EvaluateExpr(*EqExpr({SharedConstant(model::NullValue()),
+                                    SharedConstant(model::NullValue())})),
+              ReturnsNull());
+}
+
+// Corresponds to eq.null_any_returnsNull in typescript
+TEST_F(EqFunctionTest, NullOperandReturnsNull) {
+  for (const auto& val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    EXPECT_THAT(
+        EvaluateExpr(*EqExpr({SharedConstant(model::NullValue()), val})),
+        ReturnsNull())
+        << "eq(null, " << ExprId(val) << ")";
+    EXPECT_THAT(
+        EvaluateExpr(*EqExpr({val, SharedConstant(model::NullValue())})),
+        ReturnsNull())
+        << "eq(" << ExprId(val) << ", null)";
+  }
+  EXPECT_THAT(
+      EvaluateExpr(*EqExpr({SharedConstant(model::NullValue()),
+                            std::make_shared<api::Field>("nonexistent")})),
+      ReturnsUnset());
+}
+
+// Corresponds to eq.nan tests in typescript
+TEST_F(EqFunctionTest, NaNComparisonsReturnFalse) {
+  auto nan_expr = SharedConstant(std::numeric_limits<double>::quiet_NaN());
+  EXPECT_THAT(EvaluateExpr(*EqExpr({nan_expr, nan_expr})),
+              Returns(testutil::Value(false)));  // NaN == NaN is false
+
+  for (const auto& num_val : ComparisonValueTestData::NumericValues()) {
+    EXPECT_THAT(EvaluateExpr(*EqExpr({nan_expr, num_val})),
+                Returns(testutil::Value(false)))
+        << "eq(NaN, " << ExprId(num_val) << ")";
+    EXPECT_THAT(EvaluateExpr(*EqExpr({num_val, nan_expr})),
+                Returns(testutil::Value(false)))
+        << "eq(" << ExprId(num_val) << ", NaN)";
+  }
+
+  for (const auto& other_val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    bool is_numeric = false;
+    for (const auto& num_val : ComparisonValueTestData::NumericValues()) {
+      if (other_val == num_val) {
+        is_numeric = true;
+        break;
+      }
+    }
+    if (!is_numeric) {
+      EXPECT_THAT(EvaluateExpr(*EqExpr({nan_expr, other_val})),
+                  Returns(testutil::Value(false)))
+          << "eq(NaN, " << ExprId(other_val) << ")";
+      EXPECT_THAT(EvaluateExpr(*EqExpr({other_val, nan_expr})),
+                  Returns(testutil::Value(false)))
+          << "eq(" << ExprId(other_val) << ", NaN)";
+    }
+  }
+
+  EXPECT_THAT(
+      EvaluateExpr(*EqExpr({SharedConstant(testutil::Array(testutil::Value(
+                                std::numeric_limits<double>::quiet_NaN()))),
+                            SharedConstant(testutil::Array(testutil::Value(
+                                std::numeric_limits<double>::quiet_NaN())))})),
+      Returns(testutil::Value(false)));
+  EXPECT_THAT(
+      EvaluateExpr(*EqExpr(
+          {SharedConstant(testutil::Map(
+               "foo",
+               testutil::Value(std::numeric_limits<double>::quiet_NaN()))),
+           SharedConstant(testutil::Map(
+               "foo",
+               testutil::Value(std::numeric_limits<double>::quiet_NaN())))})),
+      Returns(testutil::Value(false)));
+}
+
+// Corresponds to eq.nullInArray_equality / eq.nullInMap_equality /
+// eq.null_missingInMap_equality
+TEST_F(EqFunctionTest, NullContainerEquality) {
+  auto null_array = SharedConstant(testutil::Array(testutil::Value(nullptr)));
+  EXPECT_THAT(EvaluateExpr(*EqExpr({null_array, SharedConstant(1LL)})),
+              Returns(testutil::Value(false)));
+  EXPECT_THAT(EvaluateExpr(*EqExpr({null_array, SharedConstant("1")})),
+              Returns(testutil::Value(false)));
+  EXPECT_THAT(
+      EvaluateExpr(*EqExpr({null_array, SharedConstant(model::NullValue())})),
+      ReturnsNull());
+  EXPECT_THAT(EvaluateExpr(*EqExpr(
+                  {null_array,
+                   SharedConstant(std::numeric_limits<double>::quiet_NaN())})),
+              Returns(testutil::Value(false)));
+  EXPECT_THAT(
+      EvaluateExpr(*EqExpr({null_array, SharedConstant(testutil::Array())})),
+      Returns(testutil::Value(false)));
+  EXPECT_THAT(
+      EvaluateExpr(*EqExpr(
+          {null_array, SharedConstant(testutil::Array(testutil::Value(
+                           std::numeric_limits<double>::quiet_NaN())))})),
+      ReturnsNull());
+  EXPECT_THAT(
+      EvaluateExpr(*EqExpr({null_array, SharedConstant(testutil::Array(
+                                            testutil::Value(nullptr)))})),
+      ReturnsNull());
+
+  auto null_map =
+      SharedConstant(testutil::Map("foo", testutil::Value(nullptr)));
+  EXPECT_THAT(
+      EvaluateExpr(*EqExpr({null_map, SharedConstant(testutil::Map(
+                                          "foo", testutil::Value(nullptr)))})),
+      ReturnsNull());
+  EXPECT_THAT(
+      EvaluateExpr(*EqExpr({null_map, SharedConstant(testutil::Map())})),
+      Returns(testutil::Value(false)));
+}
+
+// Corresponds to eq.error_ tests
+TEST_F(EqFunctionTest, ErrorHandling) {
+  auto error_expr = std::make_shared<api::Field>("a.b");
+  auto non_map_input = testutil::Doc("coll/doc", 1, testutil::Map("a", 123));
+
+  for (const auto& val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    EXPECT_THAT(EvaluateExpr(*EqExpr({error_expr, val}), non_map_input),
+                ReturnsUnset());
+    EXPECT_THAT(EvaluateExpr(*EqExpr({val, error_expr}), non_map_input),
+                ReturnsUnset());
+  }
+  EXPECT_THAT(EvaluateExpr(*EqExpr({error_expr, error_expr}), non_map_input),
+              ReturnsUnset());
+  EXPECT_THAT(
+      EvaluateExpr(*EqExpr({error_expr, SharedConstant(model::NullValue())}),
+                   non_map_input),
+      ReturnsUnset());
+}
+
+TEST_F(EqFunctionTest, MissingFieldReturnsUnset) {
+  EXPECT_THAT(EvaluateExpr(*EqExpr({std::make_shared<api::Field>("nonexistent"),
+                                    SharedConstant(testutil::Value(1LL))})),
+              ReturnsUnset());
+  EXPECT_THAT(
+      EvaluateExpr(*EqExpr({SharedConstant(testutil::Value(1LL)),
+                            std::make_shared<api::Field>("nonexistent")})),
+      ReturnsUnset());
+}
+
+// --- Neq (!=) Tests ---
+
+TEST_F(NeqFunctionTest, EquivalentValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::EquivalentValues()) {
+    EXPECT_THAT(EvaluateExpr(*NeqExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "neq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(NeqFunctionTest, LessThanValuesReturnTrue) {
+  for (const auto& pair : ComparisonValueTestData::LessThanValues()) {
+    EXPECT_THAT(EvaluateExpr(*NeqExpr({pair.first, pair.second})),
+                Returns(testutil::Value(true)))
+        << "neq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(NeqFunctionTest, GreaterThanValuesReturnTrue) {
+  for (const auto& pair : ComparisonValueTestData::GreaterThanValues()) {
+    EXPECT_THAT(EvaluateExpr(*NeqExpr({pair.first, pair.second})),
+                Returns(testutil::Value(true)))
+        << "neq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(NeqFunctionTest, MixedTypeValuesReturnTrue) {
+  for (const auto& pair : ComparisonValueTestData::MixedTypeValues()) {
+    EXPECT_THAT(EvaluateExpr(*NeqExpr({pair.first, pair.second})),
+                Returns(testutil::Value(true)))
+        << "neq(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+// --- Specific Neq Tests ---
+
+TEST_F(NeqFunctionTest, NullNotEqualsNullReturnsNull) {
+  EXPECT_THAT(EvaluateExpr(*NeqExpr({SharedConstant(model::NullValue()),
+                                     SharedConstant(model::NullValue())})),
+              ReturnsNull());
+}
+
+// Corresponds to neq.null_any_returnsNull
+TEST_F(NeqFunctionTest, NullOperandReturnsNull) {
+  for (const auto& val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    EXPECT_THAT(
+        EvaluateExpr(*NeqExpr({SharedConstant(model::NullValue()), val})),
+        ReturnsNull())
+        << "neq(null, " << ExprId(val) << ")";
+    EXPECT_THAT(
+        EvaluateExpr(*NeqExpr({val, SharedConstant(model::NullValue())})),
+        ReturnsNull())
+        << "neq(" << ExprId(val) << ", null)";
+  }
+  EXPECT_THAT(
+      EvaluateExpr(*NeqExpr({SharedConstant(model::NullValue()),
+                             std::make_shared<api::Field>("nonexistent")})),
+      ReturnsUnset());
+}
+
+// Corresponds to neq.nan tests
+TEST_F(NeqFunctionTest, NaNComparisonsReturnTrue) {
+  auto nan_expr = SharedConstant(std::numeric_limits<double>::quiet_NaN());
+  EXPECT_THAT(EvaluateExpr(*NeqExpr({nan_expr, nan_expr})),
+              Returns(testutil::Value(true)));  // NaN != NaN is true
+
+  for (const auto& num_val : ComparisonValueTestData::NumericValues()) {
+    EXPECT_THAT(EvaluateExpr(*NeqExpr({nan_expr, num_val})),
+                Returns(testutil::Value(true)))
+        << "neq(NaN, " << ExprId(num_val) << ")";
+    EXPECT_THAT(EvaluateExpr(*NeqExpr({num_val, nan_expr})),
+                Returns(testutil::Value(true)))
+        << "neq(" << ExprId(num_val) << ", NaN)";
+  }
+
+  for (const auto& other_val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    bool is_numeric = false;
+    for (const auto& num_val : ComparisonValueTestData::NumericValues()) {
+      if (other_val == num_val) {
+        is_numeric = true;
+        break;
+      }
+    }
+    if (!is_numeric) {
+      EXPECT_THAT(EvaluateExpr(*NeqExpr({nan_expr, other_val})),
+                  Returns(testutil::Value(true)))
+          << "neq(NaN, " << ExprId(other_val) << ")";
+      EXPECT_THAT(EvaluateExpr(*NeqExpr({other_val, nan_expr})),
+                  Returns(testutil::Value(true)))
+          << "neq(" << ExprId(other_val) << ", NaN)";
+    }
+  }
+
+  EXPECT_THAT(
+      EvaluateExpr(*NeqExpr({SharedConstant(testutil::Array(testutil::Value(
+                                 std::numeric_limits<double>::quiet_NaN()))),
+                             SharedConstant(testutil::Array(testutil::Value(
+                                 std::numeric_limits<double>::quiet_NaN())))})),
+      Returns(testutil::Value(true)));
+  EXPECT_THAT(
+      EvaluateExpr(*NeqExpr(
+          {SharedConstant(testutil::Map(
+               "foo",
+               testutil::Value(std::numeric_limits<double>::quiet_NaN()))),
+           SharedConstant(testutil::Map(
+               "foo",
+               testutil::Value(std::numeric_limits<double>::quiet_NaN())))})),
+      Returns(testutil::Value(true)));
+}
 
-using testutil::Doc;
-using testutil::Map;
+// Corresponds to neq.error_ tests
+TEST_F(NeqFunctionTest, ErrorHandling) {
+  auto error_expr = std::make_shared<api::Field>("a.b");
+  auto non_map_input = testutil::Doc("coll/doc", 1, testutil::Map("a", 123));
+
+  for (const auto& val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    EXPECT_THAT(EvaluateExpr(*NeqExpr({error_expr, val}), non_map_input),
+                ReturnsUnset());
+    EXPECT_THAT(EvaluateExpr(*NeqExpr({val, error_expr}), non_map_input),
+                ReturnsUnset());
+  }
+  EXPECT_THAT(EvaluateExpr(*NeqExpr({error_expr, error_expr}), non_map_input),
+              ReturnsUnset());
+  EXPECT_THAT(
+      EvaluateExpr(*NeqExpr({error_expr, SharedConstant(model::NullValue())}),
+                   non_map_input),
+      ReturnsUnset());
+}
+
+TEST_F(NeqFunctionTest, MissingFieldReturnsUnset) {
+  EXPECT_THAT(
+      EvaluateExpr(*NeqExpr({std::make_shared<api::Field>("nonexistent"),
+                             SharedConstant(testutil::Value(1LL))})),
+      ReturnsUnset());
+  EXPECT_THAT(
+      EvaluateExpr(*NeqExpr({SharedConstant(testutil::Value(1LL)),
+                             std::make_shared<api::Field>("nonexistent")})),
+      ReturnsUnset());
+}
+
+// --- Lt (<) Tests ---
+
+TEST_F(LtFunctionTest, EquivalentValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::EquivalentValues()) {
+    EXPECT_THAT(EvaluateExpr(*LtExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "lt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(LtFunctionTest, LessThanValuesReturnTrue) {
+  for (const auto& pair : ComparisonValueTestData::LessThanValues()) {
+    auto left_const =
+        std::dynamic_pointer_cast<const api::Constant>(pair.first);
+    auto right_const =
+        std::dynamic_pointer_cast<const api::Constant>(pair.second);
+    // Use model::Equals to check for non-equal comparable pairs
+    EXPECT_THAT(EvaluateExpr(*LtExpr({pair.first, pair.second})),
+                Returns(testutil::Value(true)))
+        << "lt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(LtFunctionTest, GreaterThanValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::GreaterThanValues()) {
+    EXPECT_THAT(EvaluateExpr(*LtExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "lt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(LtFunctionTest, MixedTypeValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::MixedTypeValues()) {
+    EXPECT_THAT(EvaluateExpr(*LtExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "lt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+// --- Specific Lt Tests ---
+
+TEST_F(LtFunctionTest, NullOperandReturnsNull) {
+  for (const auto& val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    EXPECT_THAT(
+        EvaluateExpr(*LtExpr({SharedConstant(model::NullValue()), val})),
+        ReturnsNull())
+        << "lt(null, " << ExprId(val) << ")";
+    EXPECT_THAT(
+        EvaluateExpr(*LtExpr({val, SharedConstant(model::NullValue())})),
+        ReturnsNull())
+        << "lt(" << ExprId(val) << ", null)";
+  }
+  EXPECT_THAT(EvaluateExpr(*LtExpr({SharedConstant(model::NullValue()),
+                                    SharedConstant(model::NullValue())})),
+              ReturnsNull());
+  EXPECT_THAT(
+      EvaluateExpr(*LtExpr({SharedConstant(model::NullValue()),
+                            std::make_shared<api::Field>("nonexistent")})),
+      ReturnsUnset());
+}
 
-TEST(Eq, Basic) {
-  auto result = eq(api::Field("foo"), constant(42))
-                    .ToEvaluable()
-                    ->Evaluate(NewContext(), Doc("docs/1", 0, Map("foo", 42)));
+TEST_F(LtFunctionTest, NaNComparisonsReturnFalse) {
+  auto nan_expr = SharedConstant(std::numeric_limits<double>::quiet_NaN());
+  EXPECT_THAT(EvaluateExpr(*LtExpr({nan_expr, nan_expr})),
+              Returns(testutil::Value(false)));
+
+  for (const auto& num_val : ComparisonValueTestData::NumericValues()) {
+    EXPECT_THAT(EvaluateExpr(*LtExpr({nan_expr, num_val})),
+                Returns(testutil::Value(false)))
+        << "lt(NaN, " << ExprId(num_val) << ")";
+    EXPECT_THAT(EvaluateExpr(*LtExpr({num_val, nan_expr})),
+                Returns(testutil::Value(false)))
+        << "lt(" << ExprId(num_val) << ", NaN)";
+  }
+  for (const auto& other_val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    bool is_numeric = false;
+    for (const auto& num_val : ComparisonValueTestData::NumericValues()) {
+      if (other_val == num_val) {
+        is_numeric = true;
+        break;
+      }
+    }
+    if (!is_numeric) {
+      EXPECT_THAT(EvaluateExpr(*LtExpr({nan_expr, other_val})),
+                  Returns(testutil::Value(false)))
+          << "lt(NaN, " << ExprId(other_val) << ")";
+      EXPECT_THAT(EvaluateExpr(*LtExpr({other_val, nan_expr})),
+                  Returns(testutil::Value(false)))
+          << "lt(" << ExprId(other_val) << ", NaN)";
+    }
+  }
+  EXPECT_THAT(
+      EvaluateExpr(*LtExpr({SharedConstant(testutil::Array(testutil::Value(
+                                std::numeric_limits<double>::quiet_NaN()))),
+                            SharedConstant(testutil::Array(testutil::Value(
+                                std::numeric_limits<double>::quiet_NaN())))})),
+      Returns(testutil::Value(false)));
+}
+
+TEST_F(LtFunctionTest, ErrorHandling) {
+  auto error_expr = std::make_shared<api::Field>("a.b");
+  auto non_map_input = testutil::Doc("coll/doc", 1, testutil::Map("a", 123));
+
+  for (const auto& val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    EXPECT_THAT(EvaluateExpr(*LtExpr({error_expr, val}), non_map_input),
+                ReturnsUnset());
+    EXPECT_THAT(EvaluateExpr(*LtExpr({val, error_expr}), non_map_input),
+                ReturnsUnset());
+  }
+  EXPECT_THAT(EvaluateExpr(*LtExpr({error_expr, error_expr}), non_map_input),
+              ReturnsUnset());
+  EXPECT_THAT(
+      EvaluateExpr(*LtExpr({error_expr, SharedConstant(model::NullValue())}),
+                   non_map_input),
+      ReturnsUnset());
+}
+
+TEST_F(LtFunctionTest, MissingFieldReturnsUnset) {
+  EXPECT_THAT(EvaluateExpr(*LtExpr({std::make_shared<api::Field>("nonexistent"),
+                                    SharedConstant(testutil::Value(1LL))})),
+              ReturnsUnset());
+  EXPECT_THAT(
+      EvaluateExpr(*LtExpr({SharedConstant(testutil::Value(1LL)),
+                            std::make_shared<api::Field>("nonexistent")})),
+      ReturnsUnset());
+}
+
+// --- Lte (<=) Tests ---
+
+TEST_F(LteFunctionTest, EquivalentValuesReturnTrue) {
+  for (const auto& pair : ComparisonValueTestData::EquivalentValues()) {
+    EXPECT_THAT(EvaluateExpr(*LteExpr({pair.first, pair.second})),
+                Returns(testutil::Value(true)))
+        << "lte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(LteFunctionTest, LessThanValuesReturnTrue) {
+  for (const auto& pair : ComparisonValueTestData::LessThanValues()) {
+    EXPECT_THAT(EvaluateExpr(*LteExpr({pair.first, pair.second})),
+                Returns(testutil::Value(true)))
+        << "lte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(LteFunctionTest, GreaterThanValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::GreaterThanValues()) {
+    EXPECT_THAT(EvaluateExpr(*LteExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "lte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(LteFunctionTest, MixedTypeValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::MixedTypeValues()) {
+    EXPECT_THAT(EvaluateExpr(*LteExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "lte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+// --- Specific Lte Tests ---
+
+TEST_F(LteFunctionTest, NullOperandReturnsNull) {
+  for (const auto& val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    EXPECT_THAT(
+        EvaluateExpr(*LteExpr({SharedConstant(model::NullValue()), val})),
+        ReturnsNull())
+        << "lte(null, " << ExprId(val) << ")";
+    EXPECT_THAT(
+        EvaluateExpr(*LteExpr({val, SharedConstant(model::NullValue())})),
+        ReturnsNull())
+        << "lte(" << ExprId(val) << ", null)";
+  }
+  EXPECT_THAT(EvaluateExpr(*LteExpr({SharedConstant(model::NullValue()),
+                                     SharedConstant(model::NullValue())})),
+              ReturnsNull());
+  EXPECT_THAT(
+      EvaluateExpr(*LteExpr({SharedConstant(model::NullValue()),
+                             std::make_shared<api::Field>("nonexistent")})),
+      ReturnsUnset());
+}
+
+TEST_F(LteFunctionTest, NaNComparisonsReturnFalse) {
+  auto nan_expr = SharedConstant(std::numeric_limits<double>::quiet_NaN());
+  EXPECT_THAT(EvaluateExpr(*LteExpr({nan_expr, nan_expr})),
+              Returns(testutil::Value(false)));
+
+  for (const auto& num_val : ComparisonValueTestData::NumericValues()) {
+    EXPECT_THAT(EvaluateExpr(*LteExpr({nan_expr, num_val})),
+                Returns(testutil::Value(false)))
+        << "lte(NaN, " << ExprId(num_val) << ")";
+    EXPECT_THAT(EvaluateExpr(*LteExpr({num_val, nan_expr})),
+                Returns(testutil::Value(false)))
+        << "lte(" << ExprId(num_val) << ", NaN)";
+  }
+  for (const auto& other_val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    bool is_numeric = false;
+    for (const auto& num_val : ComparisonValueTestData::NumericValues()) {
+      if (other_val == num_val) {
+        is_numeric = true;
+        break;
+      }
+    }
+    if (!is_numeric) {
+      EXPECT_THAT(EvaluateExpr(*LteExpr({nan_expr, other_val})),
+                  Returns(testutil::Value(false)))
+          << "lte(NaN, " << ExprId(other_val) << ")";
+      EXPECT_THAT(EvaluateExpr(*LteExpr({other_val, nan_expr})),
+                  Returns(testutil::Value(false)))
+          << "lte(" << ExprId(other_val) << ", NaN)";
+    }
+  }
+  EXPECT_THAT(
+      EvaluateExpr(*LteExpr({SharedConstant(testutil::Array(testutil::Value(
+                                 std::numeric_limits<double>::quiet_NaN()))),
+                             SharedConstant(testutil::Array(testutil::Value(
+                                 std::numeric_limits<double>::quiet_NaN())))})),
+      Returns(testutil::Value(false)));
+}
+
+TEST_F(LteFunctionTest, ErrorHandling) {
+  auto error_expr = std::make_shared<api::Field>("a.b");
+  auto non_map_input = testutil::Doc("coll/doc", 1, testutil::Map("a", 123));
+
+  for (const auto& val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    EXPECT_THAT(EvaluateExpr(*LteExpr({error_expr, val}), non_map_input),
+                ReturnsUnset());
+    EXPECT_THAT(EvaluateExpr(*LteExpr({val, error_expr}), non_map_input),
+                ReturnsUnset());
+  }
+  EXPECT_THAT(EvaluateExpr(*LteExpr({error_expr, error_expr}), non_map_input),
+              ReturnsUnset());
+  EXPECT_THAT(
+      EvaluateExpr(*LteExpr({error_expr, SharedConstant(model::NullValue())}),
+                   non_map_input),
+      ReturnsUnset());
+}
+
+TEST_F(LteFunctionTest, MissingFieldReturnsUnset) {
+  EXPECT_THAT(
+      EvaluateExpr(*LteExpr({std::make_shared<api::Field>("nonexistent"),
+                             SharedConstant(testutil::Value(1LL))})),
+      ReturnsUnset());
+  EXPECT_THAT(
+      EvaluateExpr(*LteExpr({SharedConstant(testutil::Value(1LL)),
+                             std::make_shared<api::Field>("nonexistent")})),
+      ReturnsUnset());
+}
+
+// --- Gt (>) Tests ---
+
+TEST_F(GtFunctionTest, EquivalentValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::EquivalentValues()) {
+    EXPECT_THAT(EvaluateExpr(*GtExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "gt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(GtFunctionTest, LessThanValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::LessThanValues()) {
+    EXPECT_THAT(EvaluateExpr(*GtExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "gt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(GtFunctionTest, GreaterThanValuesReturnTrue) {
+  for (const auto& pair : ComparisonValueTestData::GreaterThanValues()) {
+    // This set includes pairs like {1.0, 1} which compare as !GreaterThan.
+    // We expect false for those, true otherwise.
+    auto left_const =
+        std::dynamic_pointer_cast<const api::Constant>(pair.first);
+    auto right_const =
+        std::dynamic_pointer_cast<const api::Constant>(pair.second);
+    EXPECT_THAT(EvaluateExpr(*GtExpr({pair.first, pair.second})),
+                Returns(testutil::Value(true)))
+        << "gt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(GtFunctionTest, MixedTypeValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::MixedTypeValues()) {
+    EXPECT_THAT(EvaluateExpr(*GtExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "gt(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+// --- Specific Gt Tests ---
+
+TEST_F(GtFunctionTest, NullOperandReturnsNull) {
+  for (const auto& val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    EXPECT_THAT(
+        EvaluateExpr(*GtExpr({SharedConstant(model::NullValue()), val})),
+        ReturnsNull())
+        << "gt(null, " << ExprId(val) << ")";
+    EXPECT_THAT(
+        EvaluateExpr(*GtExpr({val, SharedConstant(model::NullValue())})),
+        ReturnsNull())
+        << "gt(" << ExprId(val) << ", null)";
+  }
+  EXPECT_THAT(EvaluateExpr(*GtExpr({SharedConstant(model::NullValue()),
+                                    SharedConstant(model::NullValue())})),
+              ReturnsNull());
+  EXPECT_THAT(
+      EvaluateExpr(*GtExpr({SharedConstant(model::NullValue()),
+                            std::make_shared<api::Field>("nonexistent")})),
+      ReturnsUnset());
+}
+
+TEST_F(GtFunctionTest, NaNComparisonsReturnFalse) {
+  auto nan_expr = SharedConstant(std::numeric_limits<double>::quiet_NaN());
+  EXPECT_THAT(EvaluateExpr(*GtExpr({nan_expr, nan_expr})),
+              Returns(testutil::Value(false)));
+
+  for (const auto& num_val : ComparisonValueTestData::NumericValues()) {
+    EXPECT_THAT(EvaluateExpr(*GtExpr({nan_expr, num_val})),
+                Returns(testutil::Value(false)))
+        << "gt(NaN, " << ExprId(num_val) << ")";
+    EXPECT_THAT(EvaluateExpr(*GtExpr({num_val, nan_expr})),
+                Returns(testutil::Value(false)))
+        << "gt(" << ExprId(num_val) << ", NaN)";
+  }
+  for (const auto& other_val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    bool is_numeric = false;
+    for (const auto& num_val : ComparisonValueTestData::NumericValues()) {
+      if (other_val == num_val) {
+        is_numeric = true;
+        break;
+      }
+    }
+    if (!is_numeric) {
+      EXPECT_THAT(EvaluateExpr(*GtExpr({nan_expr, other_val})),
+                  Returns(testutil::Value(false)))
+          << "gt(NaN, " << ExprId(other_val) << ")";
+      EXPECT_THAT(EvaluateExpr(*GtExpr({other_val, nan_expr})),
+                  Returns(testutil::Value(false)))
+          << "gt(" << ExprId(other_val) << ", NaN)";
+    }
+  }
+  EXPECT_THAT(
+      EvaluateExpr(*GtExpr({SharedConstant(testutil::Array(testutil::Value(
+                                std::numeric_limits<double>::quiet_NaN()))),
+                            SharedConstant(testutil::Array(testutil::Value(
+                                std::numeric_limits<double>::quiet_NaN())))})),
+      Returns(testutil::Value(false)));
+}
+
+TEST_F(GtFunctionTest, ErrorHandling) {
+  auto error_expr = std::make_shared<api::Field>("a.b");
+  auto non_map_input = testutil::Doc("coll/doc", 1, testutil::Map("a", 123));
+
+  for (const auto& val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    EXPECT_THAT(EvaluateExpr(*GtExpr({error_expr, val}), non_map_input),
+                ReturnsUnset());
+    EXPECT_THAT(EvaluateExpr(*GtExpr({val, error_expr}), non_map_input),
+                ReturnsUnset());
+  }
+  EXPECT_THAT(EvaluateExpr(*GtExpr({error_expr, error_expr}), non_map_input),
+              ReturnsUnset());
+  EXPECT_THAT(
+      EvaluateExpr(*GtExpr({error_expr, SharedConstant(model::NullValue())}),
+                   non_map_input),
+      ReturnsUnset());
+}
+
+TEST_F(GtFunctionTest, MissingFieldReturnsUnset) {
+  EXPECT_THAT(EvaluateExpr(*GtExpr({std::make_shared<api::Field>("nonexistent"),
+                                    SharedConstant(testutil::Value(1LL))})),
+              ReturnsUnset());
+  EXPECT_THAT(
+      EvaluateExpr(*GtExpr({SharedConstant(testutil::Value(1LL)),
+                            std::make_shared<api::Field>("nonexistent")})),
+      ReturnsUnset());
+}
+
+// --- Gte (>=) Tests ---
+
+TEST_F(GteFunctionTest, EquivalentValuesReturnTrue) {
+  for (const auto& pair : ComparisonValueTestData::EquivalentValues()) {
+    EXPECT_THAT(EvaluateExpr(*GteExpr({pair.first, pair.second})),
+                Returns(testutil::Value(true)))
+        << "gte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(GteFunctionTest, LessThanValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::LessThanValues()) {
+    EXPECT_THAT(EvaluateExpr(*GteExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "gte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(GteFunctionTest, GreaterThanValuesReturnTrue) {
+  for (const auto& pair : ComparisonValueTestData::GreaterThanValues()) {
+    EXPECT_THAT(EvaluateExpr(*GteExpr({pair.first, pair.second})),
+                Returns(testutil::Value(true)))
+        << "gte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+TEST_F(GteFunctionTest, MixedTypeValuesReturnFalse) {
+  for (const auto& pair : ComparisonValueTestData::MixedTypeValues()) {
+    EXPECT_THAT(EvaluateExpr(*GteExpr({pair.first, pair.second})),
+                Returns(testutil::Value(false)))
+        << "gte(" << ExprId(pair.first) << ", " << ExprId(pair.second) << ")";
+  }
+}
+
+// --- Specific Gte Tests ---
+
+TEST_F(GteFunctionTest, NullOperandReturnsNull) {
+  for (const auto& val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    EXPECT_THAT(
+        EvaluateExpr(*GteExpr({SharedConstant(model::NullValue()), val})),
+        ReturnsNull())
+        << "gte(null, " << ExprId(val) << ")";
+    EXPECT_THAT(
+        EvaluateExpr(*GteExpr({val, SharedConstant(model::NullValue())})),
+        ReturnsNull())
+        << "gte(" << ExprId(val) << ", null)";
+  }
+  EXPECT_THAT(EvaluateExpr(*GteExpr({SharedConstant(model::NullValue()),
+                                     SharedConstant(model::NullValue())})),
+              ReturnsNull());
+  EXPECT_THAT(
+      EvaluateExpr(*GteExpr({SharedConstant(model::NullValue()),
+                             std::make_shared<api::Field>("nonexistent")})),
+      ReturnsUnset());
+}
+
+TEST_F(GteFunctionTest, NaNComparisonsReturnFalse) {
+  auto nan_expr = SharedConstant(std::numeric_limits<double>::quiet_NaN());
+  EXPECT_THAT(EvaluateExpr(*GteExpr({nan_expr, nan_expr})),
+              Returns(testutil::Value(false)));
+
+  for (const auto& num_val : ComparisonValueTestData::NumericValues()) {
+    EXPECT_THAT(EvaluateExpr(*GteExpr({nan_expr, num_val})),
+                Returns(testutil::Value(false)))
+        << "gte(NaN, " << ExprId(num_val) << ")";
+    EXPECT_THAT(EvaluateExpr(*GteExpr({num_val, nan_expr})),
+                Returns(testutil::Value(false)))
+        << "gte(" << ExprId(num_val) << ", NaN)";
+  }
+  for (const auto& other_val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    bool is_numeric = false;
+    for (const auto& num_val : ComparisonValueTestData::NumericValues()) {
+      if (other_val == num_val) {
+        is_numeric = true;
+        break;
+      }
+    }
+    if (!is_numeric) {
+      EXPECT_THAT(EvaluateExpr(*GteExpr({nan_expr, other_val})),
+                  Returns(testutil::Value(false)))
+          << "gte(NaN, " << ExprId(other_val) << ")";
+      EXPECT_THAT(EvaluateExpr(*GteExpr({other_val, nan_expr})),
+                  Returns(testutil::Value(false)))
+          << "gte(" << ExprId(other_val) << ", NaN)";
+    }
+  }
+  EXPECT_THAT(
+      EvaluateExpr(*GteExpr({SharedConstant(testutil::Array(testutil::Value(
+                                 std::numeric_limits<double>::quiet_NaN()))),
+                             SharedConstant(testutil::Array(testutil::Value(
+                                 std::numeric_limits<double>::quiet_NaN())))})),
+      Returns(testutil::Value(false)));
+}
+
+TEST_F(GteFunctionTest, ErrorHandling) {
+  auto error_expr = std::make_shared<api::Field>("a.b");
+  auto non_map_input = testutil::Doc("coll/doc", 1, testutil::Map("a", 123));
+
+  for (const auto& val :
+       ComparisonValueTestData::AllSupportedComparableValues()) {
+    EXPECT_THAT(EvaluateExpr(*GteExpr({error_expr, val}), non_map_input),
+                ReturnsUnset());
+    EXPECT_THAT(EvaluateExpr(*GteExpr({val, error_expr}), non_map_input),
+                ReturnsUnset());
+  }
+  EXPECT_THAT(EvaluateExpr(*GteExpr({error_expr, error_expr}), non_map_input),
+              ReturnsUnset());
+  EXPECT_THAT(
+      EvaluateExpr(*GteExpr({error_expr, SharedConstant(model::NullValue())}),
+                   non_map_input),
+      ReturnsUnset());
+}
 
-  ASSERT_TRUE(model::Equals(*result.value(), model::TrueValue()));
+TEST_F(GteFunctionTest, MissingFieldReturnsUnset) {
+  EXPECT_THAT(
+      EvaluateExpr(*GteExpr({std::make_shared<api::Field>("nonexistent"),
+                             SharedConstant(testutil::Value(1LL))})),
+      ReturnsUnset());
+  EXPECT_THAT(
+      EvaluateExpr(*GteExpr({SharedConstant(testutil::Value(1LL)),
+                             std::make_shared<api::Field>("nonexistent")})),
+      ReturnsUnset());
 }
 
-}  //  namespace core
-}  //  namespace firestore
-}  //  namespace firebase
+}  // namespace core
+}  // namespace firestore
+}  // namespace firebase

+ 131 - 0
Firestore/core/test/unit/testutil/expression_test_util.cc

@@ -0,0 +1,131 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/test/unit/testutil/expression_test_util.h"
+
+#include <limits>  // For std::numeric_limits
+#include <limits>  // Required for numeric_limits
+#include <memory>  // For std::shared_ptr
+#include <vector>
+
+#include "Firestore/core/include/firebase/firestore/geo_point.h"
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
+#include "Firestore/core/src/model/value_util.h"  // For Value, Array, Map, BlobValue, RefValue
+
+namespace firebase {
+namespace firestore {
+namespace testutil {
+
+// Assuming Java long maps to int64_t in C++
+const int64_t kMaxLongExactlyRepresentableAsDouble = 1LL
+                                                     << 53;  // 9007199254740992
+
+// --- Initialize Static Data Members ---
+
+const std::vector<std::shared_ptr<Expr>>
+    ComparisonValueTestData::BOOLEAN_VALUES = {SharedConstant(false),
+                                               SharedConstant(true)};
+
+const std::vector<std::shared_ptr<Expr>>
+    ComparisonValueTestData::NUMERIC_VALUES = {
+        SharedConstant(-std::numeric_limits<double>::infinity()),
+        SharedConstant(-std::numeric_limits<double>::max()),
+        SharedConstant(std::numeric_limits<int64_t>::min()),
+        SharedConstant(-kMaxLongExactlyRepresentableAsDouble),
+        SharedConstant(-1LL),
+        SharedConstant(-0.5),
+        SharedConstant(-std::numeric_limits<double>::min()),  // -MIN_NORMAL
+        SharedConstant(
+            -std::numeric_limits<double>::denorm_min()),  // -MIN_VALUE
+                                                          // (denormalized)
+        SharedConstant(
+            0.0),  // Include 0.0 (represents both 0.0 and -0.0 for ordering)
+        SharedConstant(
+            std::numeric_limits<double>::denorm_min()),      // MIN_VALUE
+                                                             // (denormalized)
+        SharedConstant(std::numeric_limits<double>::min()),  // MIN_NORMAL
+        SharedConstant(0.5),
+        SharedConstant(1LL),
+        SharedConstant(42LL),
+        SharedConstant(kMaxLongExactlyRepresentableAsDouble),
+        SharedConstant(std::numeric_limits<int64_t>::max()),
+        SharedConstant(std::numeric_limits<double>::max()),
+        SharedConstant(std::numeric_limits<double>::infinity()),
+};
+
+const std::vector<std::shared_ptr<Expr>>
+    ComparisonValueTestData::TIMESTAMP_VALUES = {
+        SharedConstant(Timestamp(-42, 0)),
+        SharedConstant(Timestamp(-42, 42000000)),  // 42 ms = 42,000,000 ns
+        SharedConstant(Timestamp(0, 0)),
+        SharedConstant(Timestamp(0, 42000000)),
+        SharedConstant(Timestamp(42, 0)),
+        SharedConstant(Timestamp(42, 42000000))};
+
+const std::vector<std::shared_ptr<Expr>>
+    ComparisonValueTestData::STRING_VALUES = {
+        SharedConstant(""), SharedConstant("abcdefgh"),
+        // SharedConstant("fouxdufafa".repeat(200)), // String repeat not std
+        // C++
+        SharedConstant("santé"), SharedConstant("santé et bonheur")};
+
+const std::vector<std::shared_ptr<Expr>> ComparisonValueTestData::BYTE_VALUES =
+    {
+        SharedConstant(*BlobValue()),  // Empty - use default constructor
+        SharedConstant(*BlobValue(0, 2, 56, 42)),  // Use variadic args
+        SharedConstant(*BlobValue(2, 26)),         // Use variadic args
+        SharedConstant(*BlobValue(2, 26, 31)),     // Use variadic args
+        // SharedConstant(*BlobValue(std::vector<uint8_t>(...))), // Large blob
+};
+
+const std::vector<std::shared_ptr<Expr>>
+    ComparisonValueTestData::ENTITY_REF_VALUES = {
+        RefConstant("foo/bar"),          RefConstant("foo/bar/qux/a"),
+        RefConstant("foo/bar/qux/bleh"), RefConstant("foo/bar/qux/hi"),
+        RefConstant("foo/bar/tonk/a"),   RefConstant("foo/baz")};
+
+const std::vector<std::shared_ptr<Expr>> ComparisonValueTestData::GEO_VALUES = {
+    SharedConstant(GeoPoint(-87.0, -92.0)),
+    SharedConstant(GeoPoint(-87.0, 0.0)),
+    SharedConstant(GeoPoint(-87.0, 42.0)),
+    SharedConstant(GeoPoint(0.0, -92.0)),
+    SharedConstant(GeoPoint(0.0, 0.0)),
+    SharedConstant(GeoPoint(0.0, 42.0)),
+    SharedConstant(GeoPoint(42.0, -92.0)),
+    SharedConstant(GeoPoint(42.0, 0.0)),
+    SharedConstant(GeoPoint(42.0, 42.0))};
+
+const std::vector<std::shared_ptr<Expr>> ComparisonValueTestData::ARRAY_VALUES =
+    {SharedConstant(Array()),
+     SharedConstant(Array(true, 15LL)),
+     SharedConstant(Array(1LL, 2LL)),
+     SharedConstant(Array(Value(Timestamp(12, 0)))),
+     SharedConstant(Array("foo")),
+     SharedConstant(Array("foo", "bar")),
+     SharedConstant(Array(Value(GeoPoint(0, 0)))),
+     SharedConstant(Array(Map()))};
+
+const std::vector<std::shared_ptr<Expr>> ComparisonValueTestData::MAP_VALUES = {
+    SharedConstant(Map()),
+    SharedConstant(Map("ABA", "qux")),
+    SharedConstant(Map("aba", "hello")),
+    SharedConstant(Map("aba", "hello", "foo", true)),
+    SharedConstant(Map("aba", "qux")),
+    SharedConstant(Map("foo", "aaa"))};
+
+}  // namespace testutil
+}  // namespace firestore
+}  // namespace firebase

+ 470 - 0
Firestore/core/test/unit/testutil/expression_test_util.h

@@ -0,0 +1,470 @@
+/*
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_TEST_UNIT_TESTUTIL_EXPRESSION_TEST_UTIL_H_
+#define FIRESTORE_CORE_TEST_UNIT_TESTUTIL_EXPRESSION_TEST_UTIL_H_
+
+#include <algorithm>         // For std::sort
+#include <initializer_list>  // For std::initializer_list
+#include <limits>            // For std::numeric_limits
+#include <memory>            // For std::shared_ptr, std::make_shared
+#include <ostream>           // For std::ostream
+#include <string>            // For std::string
+#include <utility>           // For std::move, std::pair
+#include <vector>
+
+#include "Firestore/core/include/firebase/firestore/geo_point.h"
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
+#include "Firestore/core/src/api/expressions.h"
+#include "Firestore/core/src/api/stages.h"
+#include "Firestore/core/src/core/expressions_eval.h"
+#include "Firestore/core/src/model/database_id.h"
+#include "Firestore/core/src/model/document_key.h"
+#include "Firestore/core/src/model/mutable_document.h"
+#include "Firestore/core/src/model/object_value.h"
+#include "Firestore/core/src/model/snapshot_version.h"
+#include "Firestore/core/src/model/value_util.h"
+#include "Firestore/core/src/nanopb/message.h"
+#include "Firestore/core/src/remote/serializer.h"
+#include "Firestore/core/src/util/hard_assert.h"
+#include "Firestore/core/src/util/string_format.h"  // For StringFormat
+#include "Firestore/core/test/unit/testutil/testutil.h"
+
+#include "absl/strings/escaping.h"  // For absl::HexStringToBytes
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace firebase {
+namespace firestore {
+namespace testutil {
+
+using api::Constant;
+using api::EvaluateContext;
+using api::Expr;
+using api::FunctionExpr;
+using core::EvaluableExpr;
+using core::EvaluateResult;
+using model::DatabaseId;
+using model::DocumentKey;
+using model::GetTypeOrder;
+using model::MutableDocument;  // PipelineInputOutput is MutableDocument
+using model::ObjectValue;
+using model::SnapshotVersion;
+using nanopb::Message;
+using remote::Serializer;
+using util::StringFormat;
+
+// --- Constant Expression Helpers ---
+
+inline std::shared_ptr<Expr> SharedConstant(int64_t value) {
+  return std::make_shared<Constant>(Value(value));
+}
+
+inline std::shared_ptr<Expr> SharedConstant(double value) {
+  return std::make_shared<Constant>(Value(value));
+}
+
+inline std::shared_ptr<Expr> SharedConstant(const char* value) {
+  return std::make_shared<Constant>(Value(value));
+}
+
+inline std::shared_ptr<Expr> SharedConstant(bool value) {
+  return std::make_shared<Constant>(Value(value));
+}
+
+inline std::shared_ptr<Expr> SharedConstant(Timestamp value) {
+  return std::make_shared<Constant>(Value(value));
+}
+
+inline std::shared_ptr<Expr> SharedConstant(GeoPoint value) {
+  return std::make_shared<Constant>(Value(value));
+}
+
+// Overload for google_firestore_v1_Value
+inline std::shared_ptr<Expr> SharedConstant(
+    const google_firestore_v1_Value& value) {
+  // Constant expects a Message<Value>, so clone it.
+  return std::make_shared<Constant>(model::DeepClone(value));
+}
+
+inline std::shared_ptr<Expr> SharedConstant(
+    Message<google_firestore_v1_ArrayValue> value) {
+  // Constant expects a Message<Value>, so clone it.
+  return std::make_shared<Constant>(Value(std::move(value)));
+}
+
+inline std::shared_ptr<Expr> SharedConstant(
+    Message<google_firestore_v1_Value> value) {
+  // Constant expects a Message<Value>, so clone it.
+  return std::make_shared<Constant>(std::move(value));
+}
+
+// Helper to create a Reference Value Constant for tests
+// Needs to be defined before use in ENTITY_REF_VALUES if defined statically
+inline std::shared_ptr<Expr> RefConstant(const std::string& path) {
+  static const DatabaseId db_id("test-project", "test-database");
+  // model::RefValue returns a Message<Value>, pass its content to
+  // SharedConstant
+  return SharedConstant(
+      *model::RefValue(db_id, DocumentKey::FromPathString(path)));
+}
+
+inline std::shared_ptr<Expr> AddExpr(
+    std::initializer_list<std::shared_ptr<Expr>> params) {
+  return std::make_shared<FunctionExpr>(
+      "add", std::vector<std::shared_ptr<Expr>>(params));
+}
+
+inline std::shared_ptr<Expr> SubtractExpr(
+    std::initializer_list<std::shared_ptr<Expr>> params) {
+  return std::make_shared<FunctionExpr>(
+      "subtract", std::vector<std::shared_ptr<Expr>>(params));
+}
+
+inline std::shared_ptr<Expr> MultiplyExpr(
+    std::initializer_list<std::shared_ptr<Expr>> params) {
+  return std::make_shared<FunctionExpr>(
+      "multiply", std::vector<std::shared_ptr<Expr>>(params));
+}
+
+inline std::shared_ptr<Expr> DivideExpr(
+    std::initializer_list<std::shared_ptr<Expr>> params) {
+  return std::make_shared<FunctionExpr>(
+      "divide", std::vector<std::shared_ptr<Expr>>(params));
+}
+
+inline std::shared_ptr<Expr> ModExpr(
+    std::initializer_list<std::shared_ptr<Expr>> params) {
+  return std::make_shared<FunctionExpr>(
+      "mod", std::vector<std::shared_ptr<Expr>>(params));
+}
+
+// --- Comparison Expression Helpers ---
+
+inline std::shared_ptr<Expr> EqExpr(
+    std::initializer_list<std::shared_ptr<Expr>> params) {
+  HARD_ASSERT(params.size() == 2, "EqExpr requires exactly 2 parameters");
+  return std::make_shared<FunctionExpr>(
+      "eq", std::vector<std::shared_ptr<Expr>>(params));
+}
+
+inline std::shared_ptr<Expr> NeqExpr(
+    std::initializer_list<std::shared_ptr<Expr>> params) {
+  HARD_ASSERT(params.size() == 2, "NeqExpr requires exactly 2 parameters");
+  return std::make_shared<FunctionExpr>(
+      "neq", std::vector<std::shared_ptr<Expr>>(params));
+}
+
+inline std::shared_ptr<Expr> LtExpr(
+    std::initializer_list<std::shared_ptr<Expr>> params) {
+  HARD_ASSERT(params.size() == 2, "LtExpr requires exactly 2 parameters");
+  return std::make_shared<FunctionExpr>(
+      "lt", std::vector<std::shared_ptr<Expr>>(params));
+}
+
+inline std::shared_ptr<Expr> LteExpr(
+    std::initializer_list<std::shared_ptr<Expr>> params) {
+  HARD_ASSERT(params.size() == 2, "LteExpr requires exactly 2 parameters");
+  return std::make_shared<FunctionExpr>(
+      "lte", std::vector<std::shared_ptr<Expr>>(params));
+}
+
+inline std::shared_ptr<Expr> GtExpr(
+    std::initializer_list<std::shared_ptr<Expr>> params) {
+  HARD_ASSERT(params.size() == 2, "GtExpr requires exactly 2 parameters");
+  return std::make_shared<FunctionExpr>(
+      "gt", std::vector<std::shared_ptr<Expr>>(params));
+}
+
+inline std::shared_ptr<Expr> GteExpr(
+    std::initializer_list<std::shared_ptr<Expr>> params) {
+  HARD_ASSERT(params.size() == 2, "GteExpr requires exactly 2 parameters");
+  return std::make_shared<FunctionExpr>(
+      "gte", std::vector<std::shared_ptr<Expr>>(params));
+}
+
+// --- Comparison Test Data ---
+
+// Defines pairs of expressions for comparison testing.
+using ExprPair = std::pair<std::shared_ptr<Expr>, std::shared_ptr<Expr>>;
+
+namespace {
+// Helper to check if two expressions (assumed Constants) have comparable types.
+// Assuming Constant::value() returns the nanopb::Message<Value> object.
+bool IsTypeComparable(const std::shared_ptr<Expr>& left,
+                      const std::shared_ptr<Expr>& right) {
+  auto left_const = std::dynamic_pointer_cast<const Constant>(left);
+  auto right_const = std::dynamic_pointer_cast<const Constant>(right);
+  HARD_ASSERT(left_const && right_const,
+              "IsTypeComparable expects Constant expressions");
+  // Access the underlying nanopb message via *value()
+  return GetTypeOrder(left_const->to_proto()) ==
+         GetTypeOrder(right_const->to_proto());
+}
+}  // namespace
+
+struct ComparisonValueTestData {
+ private:
+  // Define the base value lists matching TypeScript (assumed sorted internally)
+  static const std::vector<std::shared_ptr<Expr>> BOOLEAN_VALUES;
+  static const std::vector<std::shared_ptr<Expr>> NUMERIC_VALUES;
+  static const std::vector<std::shared_ptr<Expr>> TIMESTAMP_VALUES;
+  static const std::vector<std::shared_ptr<Expr>> STRING_VALUES;
+  static const std::vector<std::shared_ptr<Expr>> BYTE_VALUES;
+  static const std::vector<std::shared_ptr<Expr>> ENTITY_REF_VALUES;
+  static const std::vector<std::shared_ptr<Expr>> GEO_VALUES;
+  static const std::vector<std::shared_ptr<Expr>> ARRAY_VALUES;
+  static const std::vector<std::shared_ptr<Expr>> MAP_VALUES;
+  // Note: VECTOR_VALUES omitted as VectorValue is not yet supported in C++
+  // expressions
+
+ public:
+  // A representative list of all comparable value types for null/error tests.
+  // Excludes NullValue itself. Concatenated in TypeOrder.
+  static const std::vector<std::shared_ptr<Expr>>&
+  AllSupportedComparableValues() {
+    static const std::vector<std::shared_ptr<Expr>> combined = [] {
+      std::vector<std::shared_ptr<Expr>> all_values;
+      // Concatenate in Firestore TypeOrder
+      all_values.insert(all_values.end(), BOOLEAN_VALUES.begin(),
+                        BOOLEAN_VALUES.end());
+      all_values.insert(all_values.end(), NUMERIC_VALUES.begin(),
+                        NUMERIC_VALUES.end());
+      all_values.insert(all_values.end(), TIMESTAMP_VALUES.begin(),
+                        TIMESTAMP_VALUES.end());
+      all_values.insert(all_values.end(), STRING_VALUES.begin(),
+                        STRING_VALUES.end());
+      all_values.insert(all_values.end(), BYTE_VALUES.begin(),
+                        BYTE_VALUES.end());
+      all_values.insert(all_values.end(), ENTITY_REF_VALUES.begin(),
+                        ENTITY_REF_VALUES.end());
+      all_values.insert(all_values.end(), GEO_VALUES.begin(), GEO_VALUES.end());
+      all_values.insert(all_values.end(), ARRAY_VALUES.begin(),
+                        ARRAY_VALUES.end());
+      all_values.insert(all_values.end(), MAP_VALUES.begin(), MAP_VALUES.end());
+      // No sort needed if base lists are sorted and concatenated correctly.
+      return all_values;
+    }();
+    return combined;
+  }
+
+  // Values that should compare as equal.
+  static std::vector<ExprPair> EquivalentValues() {
+    std::vector<ExprPair> results;
+    const auto& all_values = AllSupportedComparableValues();
+    for (const auto& value : all_values) {
+      results.push_back({value, value});
+    }
+
+    results.push_back({SharedConstant(-42LL), SharedConstant(-42.0)});
+    results.push_back({SharedConstant(-42.0), SharedConstant(-42LL)});
+    results.push_back({SharedConstant(42LL), SharedConstant(42.0)});
+    results.push_back({SharedConstant(42.0), SharedConstant(42LL)});
+
+    results.push_back({SharedConstant(0.0), SharedConstant(-0.0)});
+    results.push_back({SharedConstant(-0.0), SharedConstant(0.0)});
+
+    results.push_back({SharedConstant(0LL), SharedConstant(-0.0)});
+    results.push_back({SharedConstant(-0.0), SharedConstant(0LL)});
+
+    results.push_back({SharedConstant(0LL), SharedConstant(0.0)});
+    results.push_back({SharedConstant(0.0), SharedConstant(0LL)});
+
+    return results;
+  }
+
+  // Values where left < right. Relies on AllSupportedComparableValues being
+  // sorted.
+  static std::vector<ExprPair> LessThanValues() {
+    std::vector<ExprPair> results;
+    const auto& all_values = AllSupportedComparableValues();
+    for (size_t i = 0; i < all_values.size(); ++i) {
+      for (size_t j = i + 1; j < all_values.size(); ++j) {
+        const auto& left = all_values[i];
+        const auto& right = all_values[j];
+        if (IsTypeComparable(left, right)) {
+          // Since all_values is sorted by type then value,
+          // and i < j, if types are comparable, left < right.
+          // This includes pairs like {1, 1.0} which compare as !lessThan.
+          // The calling test needs to handle the expected result.
+          results.push_back({left, right});
+        }
+      }
+    }
+    return results;
+  }
+
+  // Values where left > right. Relies on AllSupportedComparableValues being
+  // sorted.
+  static std::vector<ExprPair> GreaterThanValues() {
+    std::vector<ExprPair> results;
+    const auto& all_values = AllSupportedComparableValues();
+    for (size_t i = 0; i < all_values.size(); ++i) {
+      for (size_t j = i + 1; j < all_values.size(); ++j) {
+        const auto& left = all_values[i];   // left is smaller
+        const auto& right = all_values[j];  // right is larger
+        if (IsTypeComparable(left, right)) {
+          // Since all_values is sorted, if types match, right > left.
+          // Add the reversed pair {right, left}.
+          // This includes pairs like {1.0, 1} which compare as !greaterThan.
+          // The calling test needs to handle the expected result.
+          results.push_back({right, left});  // Add reversed pair
+        }
+      }
+    }
+    return results;
+  }
+
+  // Values of different types.
+  static std::vector<ExprPair> MixedTypeValues() {
+    std::vector<ExprPair> results;
+    const auto& all_values = AllSupportedComparableValues();
+    for (size_t i = 0; i < all_values.size(); ++i) {
+      for (size_t j = 0; j < all_values.size(); ++j) {  // Note: j starts from 0
+        const auto& left = all_values[i];
+        const auto& right = all_values[j];
+        if (!IsTypeComparable(left, right)) {
+          results.push_back({left, right});
+        }
+      }
+    }
+    return results;
+  }
+
+  // Numeric values for NaN tests (subset of NUMERIC_VALUES)
+  static const std::vector<std::shared_ptr<Expr>>& NumericValues() {
+    return NUMERIC_VALUES;
+  }
+};
+
+static remote::Serializer serializer(model::DatabaseId("test-project"));
+
+// Creates a default evaluation context.
+inline api::EvaluateContext NewContext() {
+  return EvaluateContext{&serializer};
+}
+
+// Helper function to evaluate an expression and return the result.
+// Creates a dummy context and input document.
+inline EvaluateResult EvaluateExpr(const Expr& expr) {
+  // Use a dummy input document (FoundDocument with empty data)
+  model::PipelineInputOutput input = testutil::Doc("coll/doc", 1);
+
+  std::unique_ptr<EvaluableExpr> evaluable = expr.ToEvaluable();
+  HARD_ASSERT(evaluable != nullptr, "Failed to create evaluable expression");
+  return evaluable->Evaluate(NewContext(), input);
+}
+
+// Helper function to evaluate an expression with a specific input.
+inline EvaluateResult EvaluateExpr(const Expr& expr,
+                                   const model::PipelineInputOutput& input) {
+  std::unique_ptr<EvaluableExpr> evaluable = expr.ToEvaluable();
+  HARD_ASSERT(evaluable != nullptr, "Failed to create evaluable expression");
+  return evaluable->Evaluate(NewContext(), input);
+}
+
+// --- Custom Gmock Matchers ---
+
+MATCHER(ReturnsError, std::string("evaluates to error ")) {
+  // 'arg' is the value being tested
+  if (arg.type() == EvaluateResult::ResultType::kError) {
+    return true;
+  } else {
+    *result_listener << "the result type is "
+                     << testing::PrintToString(arg.type());
+    return false;
+  }
+}
+
+MATCHER(ReturnsNull, std::string("evaluates to null ")) {
+  // 'arg' is the value being tested
+  if (arg.type() == EvaluateResult::ResultType::kNull) {
+    return true;
+  } else {
+    *result_listener << "the result type is "
+                     << testing::PrintToString(arg.type());
+    return false;
+  }
+}
+
+MATCHER(ReturnsUnset, std::string("evaluates to unset ")) {
+  // 'arg' is the value being tested
+  if (arg.type() == EvaluateResult::ResultType::kUnset) {
+    return true;
+  } else {
+    *result_listener << "the result type is "
+                     << testing::PrintToString(arg.type());
+    return false;
+  }
+}
+
+template <typename T>
+class ReturnsMatcherImpl : public testing::MatcherInterface<T> {
+ public:
+  explicit ReturnsMatcherImpl(
+      Message<google_firestore_v1_Value>&& expected_value)
+      : expected_value_(std::move(expected_value)) {
+  }
+
+  bool MatchAndExplain(T arg,
+                       testing::MatchResultListener* listener) const override {
+    if (!arg.IsErrorOrUnset()) {
+      // Value is valid, proceed with comparison
+      if (model::IsNaNValue(*expected_value_)) {
+        *listener << "expected NaN, but got "
+                  << model::CanonicalId(*arg.value());
+        // Special handling for NaN: Both must be NaN to match
+        return model::IsNaNValue(*arg.value());
+      } else {
+        *listener << "expected value " << model::CanonicalId(*expected_value_)
+                  << ", but got " << model::CanonicalId(*arg.value());
+        // Standard equality comparison
+        return model::Equals(*arg.value(), *expected_value_);
+      }
+    } else {
+      // The actual result 'arg' is an error or unset, but we expected a value.
+      // This is considered a mismatch.
+      *listener << "expected value, but got result type"
+                << testing::PrintToString(arg.type());
+      return false;
+    }
+  }
+
+  void DescribeTo(std::ostream* os) const override {
+    *os << "evaluates to value " << testing::PrintToString(expected_value_);
+  }
+
+  void DescribeNegationTo(std::ostream* os) const override {
+    *os << "does not evaluate to value "
+        << testing::PrintToString(expected_value_);
+  }
+
+ private:
+  Message<google_firestore_v1_Value> expected_value_;
+};
+
+template <typename T = const EvaluateResult&>
+inline testing::Matcher<T> Returns(
+    Message<google_firestore_v1_Value>&& expected_value) {
+  return testing::MakeMatcher(
+      new ReturnsMatcherImpl<T>(std::move(expected_value)));
+}
+
+}  // namespace testutil
+}  // namespace firestore
+}  // namespace firebase
+
+#endif  // FIRESTORE_CORE_TEST_UNIT_TESTUTIL_EXPRESSION_TEST_UTIL_H_

+ 0 - 0
cmake/external/leveldb_patch.py