Browse Source

Sum and average (#11108)

This is implementation but not public release of sum and average. The sum/avg API is not made public. There are no API changes in this PR.
Mark Duckworth 2 years ago
parent
commit
52ebbbf19d
45 changed files with 4289 additions and 170 deletions
  1. 36 20
      Firestore/Example/Firestore.xcodeproj/project.pbxproj
  2. 8 0
      Firestore/Example/Tests/API/FSTAPIHelpers.h
  3. 14 0
      Firestore/Example/Tests/API/FSTAPIHelpers.mm
  4. 1445 0
      Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm
  5. 1 1
      Firestore/Protos/README.md
  6. 619 23
      Firestore/Protos/cpp/google/firestore/v1/query.pb.cc
  7. 552 4
      Firestore/Protos/cpp/google/firestore/v1/query.pb.h
  8. 14 3
      Firestore/Protos/nanopb/google/firestore/v1/query.nanopb.cc
  9. 29 1
      Firestore/Protos/nanopb/google/firestore/v1/query.nanopb.h
  10. 46 0
      Firestore/Protos/protos/google/firestore/v1/query.proto
  11. 62 0
      Firestore/Source/API/FIRAggregateField+Internal.h
  12. 116 0
      Firestore/Source/API/FIRAggregateField.h
  13. 130 0
      Firestore/Source/API/FIRAggregateField.mm
  14. 5 1
      Firestore/Source/API/FIRAggregateQuery+Internal.h
  15. 23 11
      Firestore/Source/API/FIRAggregateQuery.mm
  16. 31 2
      Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h
  17. 47 9
      Firestore/Source/API/FIRAggregateQuerySnapshot.mm
  18. 24 0
      Firestore/Source/API/FIRQuery+Internal.h
  19. 9 4
      Firestore/Source/API/FIRQuery.mm
  20. 401 0
      Firestore/Swift/Tests/Integration/AggregationIntegrationTests.swift
  21. 0 7
      Firestore/Swift/Tests/Integration/AsyncAwaitIntegrationTests.swift
  22. 35 6
      Firestore/core/src/api/aggregate_query.cc
  23. 23 1
      Firestore/core/src/api/aggregate_query.h
  24. 9 0
      Firestore/core/src/api/api_fwd.h
  25. 13 1
      Firestore/core/src/api/query_core.cc
  26. 20 1
      Firestore/core/src/api/query_core.h
  27. 11 5
      Firestore/core/src/core/firestore_client.cc
  28. 4 2
      Firestore/core/src/core/firestore_client.h
  29. 8 3
      Firestore/core/src/core/sync_engine.cc
  30. 8 3
      Firestore/core/src/core/sync_engine.h
  31. 39 0
      Firestore/core/src/model/aggregate_alias.cc
  32. 47 0
      Firestore/core/src/model/aggregate_alias.h
  33. 31 0
      Firestore/core/src/model/aggregate_field.cc
  34. 62 0
      Firestore/core/src/model/aggregate_field.h
  35. 44 0
      Firestore/core/src/model/object_value.cc
  36. 24 0
      Firestore/core/src/model/object_value.h
  37. 28 26
      Firestore/core/src/remote/datastore.cc
  38. 10 6
      Firestore/core/src/remote/datastore.h
  39. 92 19
      Firestore/core/src/remote/remote_objc_bridge.cc
  40. 11 4
      Firestore/core/src/remote/remote_objc_bridge.h
  41. 7 3
      Firestore/core/src/remote/remote_store.cc
  42. 7 2
      Firestore/core/src/remote/remote_store.h
  43. 1 0
      Firestore/core/test/unit/api/CMakeLists.txt
  44. 137 1
      Firestore/core/test/unit/api/aggregate_query_test.cc
  45. 6 1
      scripts/run_firestore_emulator.sh

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

@@ -65,6 +65,7 @@
 		095A878BB33211AB52BFAD9F /* leveldb_document_overlay_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AE89CFF09C6804573841397F /* leveldb_document_overlay_cache_test.cc */; };
 		0963F6D7B0F9AE1E24B82866 /* path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 403DBF6EFB541DFD01582AA3 /* path_test.cc */; };
 		096BA3A3703AC1491F281618 /* index.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 395E8B07639E69290A929695 /* index.pb.cc */; };
+		09BE8C01EC33D1FD82262D5D /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AF924C79F49F793992A84879 /* aggregate_query_test.cc */; };
 		0A4E1B5E3E853763AE6ED7AE /* grpc_stream_tester.cc in Sources */ = {isa = PBXBuildFile; fileRef = 87553338E42B8ECA05BA987E /* grpc_stream_tester.cc */; };
 		0A52B47C43B7602EE64F53A7 /* cc_compilation_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1B342370EAE3AA02393E33EB /* cc_compilation_test.cc */; };
 		0A6FBE65A7FE048BAD562A15 /* FSTGoogleTestTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 54764FAE1FAA21B90085E60A /* FSTGoogleTestTests.mm */; };
@@ -171,12 +172,6 @@
 		1B9F95F129FAD4D800EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1B9F95EC29FAD3F100EEC075 /* FIRAggregateQueryUnitTests.mm */; };
 		1B9F95F229FAD4E000EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1B9F95EC29FAD3F100EEC075 /* FIRAggregateQueryUnitTests.mm */; };
 		1B9F95F329FAD4E100EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1B9F95EC29FAD3F100EEC075 /* FIRAggregateQueryUnitTests.mm */; };
-		1B9F95F52A0933AA00EEC075 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1B9F95F42A0933AA00EEC075 /* aggregate_query_test.cc */; };
-		1B9F95F62A0933AA00EEC075 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1B9F95F42A0933AA00EEC075 /* aggregate_query_test.cc */; };
-		1B9F95F72A0933AA00EEC075 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1B9F95F42A0933AA00EEC075 /* aggregate_query_test.cc */; };
-		1B9F95F82A0933AA00EEC075 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1B9F95F42A0933AA00EEC075 /* aggregate_query_test.cc */; };
-		1B9F95F92A0933AA00EEC075 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1B9F95F42A0933AA00EEC075 /* aggregate_query_test.cc */; };
-		1B9F95FA2A0933AA00EEC075 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 1B9F95F42A0933AA00EEC075 /* aggregate_query_test.cc */; };
 		1BB0C34B2E8D8BCC5882430A /* garbage_collection_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = AAED89D7690E194EF3BA1132 /* garbage_collection_spec_test.json */; };
 		1BF1F9A0CBB6B01654D3C2BE /* field_transform_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7515B47C92ABEEC66864B55C /* field_transform_test.cc */; };
 		1C19D796DB6715368407387A /* annotations.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9520B89AAC00B5BCE7 /* annotations.pb.cc */; };
@@ -279,6 +274,7 @@
 		2E169CF1E9E499F054BB873A /* FSTEventAccumulator.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0392021401F00B64F25 /* FSTEventAccumulator.mm */; };
 		2E373EA9D5FF8C6DE2507675 /* field_index_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BF76A8DA34B5B67B4DD74666 /* field_index_test.cc */; };
 		2E76BC76BBCE5FCDDCF5EEBE /* leveldb_bundle_cache_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8E9CD82E60893DDD7757B798 /* leveldb_bundle_cache_test.cc */; };
+		2E7CAC076447970DE881E703 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AF924C79F49F793992A84879 /* aggregate_query_test.cc */; };
 		2EAD77559EC654E6CA4D3E21 /* FIRSnapshotMetadataTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04D202154AA00B64F25 /* FIRSnapshotMetadataTests.mm */; };
 		2EB2EE24076A4E4621E38E45 /* nanopb_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6F5B6C1399F92FD60F2C582B /* nanopb_util_test.cc */; };
 		2EC1C4D202A01A632339A161 /* field_transform_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 7515B47C92ABEEC66864B55C /* field_transform_test.cc */; };
@@ -362,6 +358,7 @@
 		3FFFC1FE083D8BE9C4D9A148 /* string_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AB380CFC201A2EE200D97691 /* string_util_test.cc */; };
 		40431BF2A368D0C891229F6E /* FSTMemorySpecTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E02F20213FFC00B64F25 /* FSTMemorySpecTests.mm */; };
 		409C0F2BFC2E1BECFFAC4D32 /* testutil.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54A0352820A3B3BD003E0143 /* testutil.cc */; };
+		412BE974741729A6683C386F /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AF924C79F49F793992A84879 /* aggregate_query_test.cc */; };
 		4173B61CB74EB4CD1D89EE68 /* latlng.pb.cc in Sources */ = {isa = PBXBuildFile; fileRef = 618BBE9220B89AAC00B5BCE7 /* latlng.pb.cc */; };
 		4194B7BB8B0352E1AC5D69B9 /* precondition_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA5520A36E1F00BCEB75 /* precondition_test.cc */; };
 		41EAC526C543064B8F3F7EDA /* field_path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B686F2AD2023DDB20028D6BE /* field_path_test.cc */; };
@@ -596,6 +593,7 @@
 		59F512D155DE361095A04ED4 /* FIRSnapshotMetadataTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E04D202154AA00B64F25 /* FIRSnapshotMetadataTests.mm */; };
 		5A080105CCBFDB6BF3F3772D /* path_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 403DBF6EFB541DFD01582AA3 /* path_test.cc */; };
 		5A44725457D6B7805FD66EEB /* bundle_loader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = A853C81A6A5A51C9D0389EDA /* bundle_loader_test.cc */; };
+		5ACF26A3B0A33784CC525FB0 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AF924C79F49F793992A84879 /* aggregate_query_test.cc */; };
 		5AFA1055E8F6B4E4B1CCE2C4 /* bundle_builder.cc in Sources */ = {isa = PBXBuildFile; fileRef = 4F5B96F3ABCD2CA901DB1CD4 /* bundle_builder.cc */; };
 		5B0E2D0595BE30B2320D96F1 /* EncodableFieldValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1235769122B7E915007DDFA9 /* EncodableFieldValueTests.swift */; };
 		5B4391097A6DF86EC3801DEE /* string_win_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 79507DF8378D3C42F5B36268 /* string_win_test.cc */; };
@@ -1032,6 +1030,7 @@
 		AFB2455806D7C4100C16713B /* object_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 214877F52A705012D6720CA0 /* object_value_test.cc */; };
 		AFE84E7B0C356CD2A113E56E /* status_testing.cc in Sources */ = {isa = PBXBuildFile; fileRef = 3CAA33F964042646FDDAF9F9 /* status_testing.cc */; };
 		B03F286F3AEC3781C386C646 /* FIRNumericTransformTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = D5B25E7E7D6873CBA4571841 /* FIRNumericTransformTests.mm */; };
+		B04E4FE20930384DF3A402F9 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AF924C79F49F793992A84879 /* aggregate_query_test.cc */; };
 		B0B779769926304268200015 /* query_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 731541602214AFFA0037F4DC /* query_spec_test.json */; };
 		B0D10C3451EDFB016A6EAF03 /* writer_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = BC3C788D290A935C353CEAA1 /* writer_test.cc */; };
 		B15D17049414E2F5AE72C9C6 /* memory_local_store_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = F6CA0C5638AB6627CB5B4CF4 /* memory_local_store_test.cc */; };
@@ -1266,6 +1265,7 @@
 		DE435F33CE563E238868D318 /* query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B9C261C26C5D311E1E3C0CB9 /* query_test.cc */; };
 		DE45CD044B431DB0525595A5 /* bundle_reader_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 6ECAF7DE28A19C69DF386D88 /* bundle_reader_test.cc */; };
 		DE50F1D39D34F867BC750957 /* grpc_stream_tester.cc in Sources */ = {isa = PBXBuildFile; fileRef = 87553338E42B8ECA05BA987E /* grpc_stream_tester.cc */; };
+		DEC033E4FB3E09A3C7CE6016 /* aggregate_query_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = AF924C79F49F793992A84879 /* aggregate_query_test.cc */; };
 		DEF4BF5FAA83C37100408F89 /* bundle_spec_test.json in Resources */ = {isa = PBXBuildFile; fileRef = 79EAA9F7B1B9592B5F053923 /* bundle_spec_test.json */; };
 		DF4B3835C5AA4835C01CD255 /* local_store_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 307FF03D0297024D59348EBD /* local_store_test.cc */; };
 		DF7ABEB48A650117CBEBCD26 /* object_value_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 214877F52A705012D6720CA0 /* object_value_test.cc */; };
@@ -1344,6 +1344,12 @@
 		EF3518F84255BAF3EBD317F6 /* exponential_backoff_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = B6D1B68420E2AB1A00B35856 /* exponential_backoff_test.cc */; };
 		EF43FF491B9282E0330E4CA2 /* remote_event_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 584AE2C37A55B408541A6FF3 /* remote_event_test.cc */; };
 		EF4FB3034994E6386F3C78FF /* leveldb_overlay_migration_manager_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = D8A6D52723B1BABE1B7B8D8F /* leveldb_overlay_migration_manager_test.cc */; };
+		EF6C285129E462A200A7D4F1 /* FIRAggregateTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EF6C285029E462A200A7D4F1 /* FIRAggregateTests.mm */; };
+		EF6C285229E462A200A7D4F1 /* FIRAggregateTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EF6C285029E462A200A7D4F1 /* FIRAggregateTests.mm */; };
+		EF6C285329E462A200A7D4F1 /* FIRAggregateTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = EF6C285029E462A200A7D4F1 /* FIRAggregateTests.mm */; };
+		EF6C286D29E6D22200A7D4F1 /* AggregationIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6C286C29E6D22200A7D4F1 /* AggregationIntegrationTests.swift */; };
+		EF6C286E29E6D22200A7D4F1 /* AggregationIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6C286C29E6D22200A7D4F1 /* AggregationIntegrationTests.swift */; };
+		EF6C286F29E6D22200A7D4F1 /* AggregationIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF6C286C29E6D22200A7D4F1 /* AggregationIntegrationTests.swift */; };
 		EF79998EBE4C72B97AB1880E /* value_util_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 40F9D09063A07F710811A84F /* value_util_test.cc */; };
 		EF8C005DC4BEA6256D1DBC6F /* user_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = CCC9BD953F121B9E29F9AA42 /* user_test.cc */; };
 		EFD682178A87513A5F1AEFD9 /* memory_query_engine_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 8EF6A33BC2D84233C355F1D0 /* memory_query_engine_test.cc */; };
@@ -1492,7 +1498,6 @@
 		166CE73C03AB4366AAC5201C /* leveldb_index_manager_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_index_manager_test.cc; sourceTree = "<group>"; };
 		1B342370EAE3AA02393E33EB /* cc_compilation_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = cc_compilation_test.cc; path = api/cc_compilation_test.cc; sourceTree = "<group>"; };
 		1B9F95EC29FAD3F100EEC075 /* FIRAggregateQueryUnitTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRAggregateQueryUnitTests.mm; sourceTree = "<group>"; };
-		1B9F95F42A0933AA00EEC075 /* aggregate_query_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = aggregate_query_test.cc; path = api/aggregate_query_test.cc; sourceTree = "<group>"; };
 		1C01D8CE367C56BB2624E299 /* index.pb.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = index.pb.h; path = admin/index.pb.h; sourceTree = "<group>"; };
 		1C3F7302BF4AE6CBC00ECDD0 /* resource.pb.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = resource.pb.cc; sourceTree = "<group>"; };
 		1CA9800A53669EFBFFB824E3 /* memory_remote_document_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = memory_remote_document_cache_test.cc; sourceTree = "<group>"; };
@@ -1768,6 +1773,7 @@
 		ABF6506B201131F8005F2C74 /* timestamp_test.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = timestamp_test.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; name = aggregate_query_test.cc; path = api/aggregate_query_test.cc; sourceTree = "<group>"; };
 		B3F5B3AAE791A5911B9EAA82 /* Pods-Firestore_Tests_iOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_iOS.release.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS.release.xcconfig"; sourceTree = "<group>"; };
 		B5C2A94EE24E60543F62CC35 /* bundle_serializer_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; name = bundle_serializer_test.cc; path = bundle/bundle_serializer_test.cc; sourceTree = "<group>"; };
 		B5C37696557C81A6C2B7271A /* target_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = target_cache_test.cc; sourceTree = "<group>"; };
@@ -1848,6 +1854,8 @@
 		E592181BFD7C53C305123739 /* Pods-Firestore_Tests_iOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Firestore_Tests_iOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Firestore_Tests_iOS/Pods-Firestore_Tests_iOS.debug.xcconfig"; sourceTree = "<group>"; };
 		E76F0CDF28E5FA62D21DE648 /* leveldb_target_cache_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_target_cache_test.cc; sourceTree = "<group>"; };
 		ECEBABC7E7B693BE808A1052 /* Pods_Firestore_IntegrationTests_iOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Firestore_IntegrationTests_iOS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		EF6C285029E462A200A7D4F1 /* FIRAggregateTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FIRAggregateTests.mm; sourceTree = "<group>"; };
+		EF6C286C29E6D22200A7D4F1 /* AggregationIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AggregationIntegrationTests.swift; sourceTree = "<group>"; };
 		EF83ACD5E1E9F25845A9ACED /* leveldb_migrations_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_migrations_test.cc; sourceTree = "<group>"; };
 		F02F734F272C3C70D1307076 /* filter_test.cc */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.cpp; path = filter_test.cc; sourceTree = "<group>"; };
 		F119BDDF2F06B3C0883B8297 /* firebase_app_check_credentials_provider_test.mm */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.cpp.objcpp; name = firebase_app_check_credentials_provider_test.mm; path = credentials/firebase_app_check_credentials_provider_test.mm; sourceTree = "<group>"; };
@@ -1986,6 +1994,7 @@
 		124C932A22C1635300CA8C2D /* Integration */ = {
 			isa = PBXGroup;
 			children = (
+				EF6C286C29E6D22200A7D4F1 /* AggregationIntegrationTests.swift */,
 				062072B62773A055001655D7 /* AsyncAwaitIntegrationTests.swift */,
 				124C932B22C1642C00CA8C2D /* CodableIntegrationTests.swift */,
 				3355BE9391CC4857AF0BDAE3 /* DatabaseTests.swift */,
@@ -2522,7 +2531,7 @@
 		8FC5BFAD63BAC5AADAC8A94A /* api */ = {
 			isa = PBXGroup;
 			children = (
-				1B9F95F42A0933AA00EEC075 /* aggregate_query_test.cc */,
+				AF924C79F49F793992A84879 /* aggregate_query_test.cc */,
 				1B342370EAE3AA02393E33EB /* cc_compilation_test.cc */,
 				8F1A7B4158D9DD76EE4836BF /* load_bundle_task_test.cc */,
 				DD12BC1DB2480886D2FB0005 /* settings_test.cc */,
@@ -2724,6 +2733,7 @@
 		DE51B1BC1F0D48AC0013853F /* API */ = {
 			isa = PBXGroup;
 			children = (
+				EF6C285029E462A200A7D4F1 /* FIRAggregateTests.mm */,
 				73866A9F2082B069009BB4FF /* FIRArrayTransformTests.mm */,
 				776530F066E788C355B78457 /* FIRBundlesTests.mm */,
 				129A369928CA555B005AE7E2 /* FIRCountTests.mm */,
@@ -3670,6 +3680,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				1B9F95F029FAD4D700EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */,
 				E11DDA3DD75705F26245E295 /* FIRCollectionReferenceTests.mm in Sources */,
 				46999832F7D1709B4C29FAA8 /* FIRDocumentReferenceTests.mm in Sources */,
 				6FD2369F24E884A9D767DD80 /* FIRDocumentSnapshotTests.mm in Sources */,
@@ -3686,7 +3697,6 @@
 				F4F00BF4E87D7F0F0F8831DB /* FSTEventAccumulator.mm in Sources */,
 				0A6FBE65A7FE048BAD562A15 /* FSTGoogleTestTests.mm in Sources */,
 				939C898FE9D129F6A2EA259C /* FSTHelpers.mm in Sources */,
-				1B9F95F72A0933AA00EEC075 /* aggregate_query_test.cc in Sources */,
 				C4055D868A38221B332CD03D /* FSTIntegrationTestCase.mm in Sources */,
 				EC80A217F3D66EB0272B36B0 /* FSTLevelDBSpecTests.mm in Sources */,
 				6FF2B680CC8631B06C7BD7AB /* FSTMemorySpecTests.mm in Sources */,
@@ -3695,6 +3705,7 @@
 				072D805A94E767DE4D371881 /* FSTSyncEngineTestDriver.mm in Sources */,
 				5424AB6FF5035495C03344E7 /* FSTUserDataReaderTests.mm in Sources */,
 				6DCA8E54E652B78EFF3EEDAC /* XCTestCase+Await.mm in Sources */,
+				5ACF26A3B0A33784CC525FB0 /* aggregate_query_test.cc in Sources */,
 				C8BA36C8B5E26C173F91E677 /* aggregation_result.pb.cc in Sources */,
 				45939AFF906155EA27D281AB /* annotations.pb.cc in Sources */,
 				FF3405218188DFCE586FB26B /* app_testing.mm in Sources */,
@@ -3821,7 +3832,6 @@
 				FE9131E2D84A560D287B6F90 /* resource.pb.cc in Sources */,
 				C7F174164D7C55E35A526009 /* resource_path_test.cc in Sources */,
 				2836CD14F6F0EA3B184E325E /* schedule_test.cc in Sources */,
-				1B9F95F029FAD4D700EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */,
 				4DAF501EE4B4DB79ED4239B0 /* secure_random_test.cc in Sources */,
 				D57F4CB3C92CE3D4DF329B78 /* serializer_test.cc in Sources */,
 				4C5292BF643BF14FA2AC5DB1 /* settings_test.cc in Sources */,
@@ -3882,6 +3892,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				1B9F95F229FAD4E000EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */,
 				00B7AFE2A7C158DD685EB5EE /* FIRCollectionReferenceTests.mm in Sources */,
 				25FE27330996A59F31713A0C /* FIRDocumentReferenceTests.mm in Sources */,
 				28E4B4A53A739AE2C9CF4159 /* FIRDocumentSnapshotTests.mm in Sources */,
@@ -3898,7 +3909,6 @@
 				73E42D984FB36173A2BDA57C /* FSTEventAccumulator.mm in Sources */,
 				E375FBA0632EFB4D14C4E5A9 /* FSTGoogleTestTests.mm in Sources */,
 				F72DF72447EA7AB9D100816A /* FSTHelpers.mm in Sources */,
-				1B9F95F92A0933AA00EEC075 /* aggregate_query_test.cc in Sources */,
 				AEBF3F80ACC01AA8A27091CD /* FSTIntegrationTestCase.mm in Sources */,
 				7495E3BAE536CD839EE20F31 /* FSTLevelDBSpecTests.mm in Sources */,
 				EB04FE18E5794FEC187A09E3 /* FSTMemorySpecTests.mm in Sources */,
@@ -3907,6 +3917,7 @@
 				D69B97FF4C065EACEDD91886 /* FSTSyncEngineTestDriver.mm in Sources */,
 				A124744C6CBEF3DD415A1A72 /* FSTUserDataReaderTests.mm in Sources */,
 				AAC15E7CCAE79619B2ABB972 /* XCTestCase+Await.mm in Sources */,
+				DEC033E4FB3E09A3C7CE6016 /* aggregate_query_test.cc in Sources */,
 				156429A2993B86A905A42D96 /* aggregation_result.pb.cc in Sources */,
 				1C19D796DB6715368407387A /* annotations.pb.cc in Sources */,
 				6EEA00A737690EF82A3C91C6 /* app_testing.mm in Sources */,
@@ -4033,7 +4044,6 @@
 				0929C73B3F3BFC331E9E9D2F /* resource.pb.cc in Sources */,
 				85B8918FC8C5DC62482E39C3 /* resource_path_test.cc in Sources */,
 				7F6199159E24E19E2A3F5601 /* schedule_test.cc in Sources */,
-				1B9F95F229FAD4E000EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */,
 				A8C9FF6D13E6C83D4AB54EA7 /* secure_random_test.cc in Sources */,
 				31A396C81A107D1DEFDF4A34 /* serializer_test.cc in Sources */,
 				086A8CEDD4C4D5C858498C2D /* settings_test.cc in Sources */,
@@ -4084,6 +4094,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				EF6C286F29E6D22200A7D4F1 /* AggregationIntegrationTests.swift in Sources */,
 				062072B92773A055001655D7 /* AsyncAwaitIntegrationTests.swift in Sources */,
 				733AFC467B600967536BD70F /* BasicCompileTests.swift in Sources */,
 				79987AF2DF1FCE799008B846 /* CodableGeoPointTests.swift in Sources */,
@@ -4092,6 +4103,8 @@
 				AC835157AD2BE7AA8D20FB5A /* ConditionalConformanceTests.swift in Sources */,
 				2F69187F601E00054469F4A5 /* DatabaseTests.swift in Sources */,
 				816E8E62DC163649BA96951C /* EncodableFieldValueTests.swift in Sources */,
+				1B9F95F329FAD4E100EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */,
+				EF6C285329E462A200A7D4F1 /* FIRAggregateTests.mm in Sources */,
 				95ED06D2B0078D3CDB821B68 /* FIRArrayTransformTests.mm in Sources */,
 				DB3ADDA51FB93E84142EA90D /* FIRBundlesTests.mm in Sources */,
 				0500A324CEC854C5B0CF364C /* FIRCollectionReferenceTests.mm in Sources */,
@@ -4136,6 +4149,7 @@
 				3B1E27D951407FD237E64D07 /* FirestoreEncoderTests.swift in Sources */,
 				621D620C28F9CE7400D2FA26 /* QueryIntegrationTests.swift in Sources */,
 				4D42E5C756229C08560DD731 /* XCTestCase+Await.mm in Sources */,
+				09BE8C01EC33D1FD82262D5D /* aggregate_query_test.cc in Sources */,
 				0EC3921AE220410F7394729B /* aggregation_result.pb.cc in Sources */,
 				276A563D546698B6AAC20164 /* annotations.pb.cc in Sources */,
 				7B8D7BAC1A075DB773230505 /* app_testing.mm in Sources */,
@@ -4148,7 +4162,6 @@
 				146C140B254F3837A4DD7AE8 /* bits_test.cc in Sources */,
 				3DDC57212ADBA9AD498EAA4C /* bundle.pb.cc in Sources */,
 				F3DEF2DB11FADAABDAA4C8BB /* bundle_builder.cc in Sources */,
-				1B9F95FA2A0933AA00EEC075 /* aggregate_query_test.cc in Sources */,
 				392966346DA5EB3165E16A22 /* bundle_cache_test.cc in Sources */,
 				CE411D4B70353823DE63C0D5 /* bundle_loader_test.cc in Sources */,
 				DE45CD044B431DB0525595A5 /* bundle_reader_test.cc in Sources */,
@@ -4215,7 +4228,6 @@
 				AD89E95440264713557FB38E /* leveldb_migrations_test.cc in Sources */,
 				FE701C2D739A5371BCBD62B9 /* leveldb_mutation_queue_test.cc in Sources */,
 				98FE82875A899A40A98AAC22 /* leveldb_opener_test.cc in Sources */,
-				1B9F95F329FAD4E100EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */,
 				6F256C06FCBA46378EC35D72 /* leveldb_overlay_migration_manager_test.cc in Sources */,
 				153DBBCAF6D4FFA8ABC2EBDF /* leveldb_query_engine_test.cc in Sources */,
 				79D86DD18BB54D2D69DC457F /* leveldb_remote_document_cache_test.cc in Sources */,
@@ -4314,6 +4326,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				EF6C286E29E6D22200A7D4F1 /* AggregationIntegrationTests.swift in Sources */,
 				062072B82773A055001655D7 /* AsyncAwaitIntegrationTests.swift in Sources */,
 				B896E5DE1CC27347FAC009C3 /* BasicCompileTests.swift in Sources */,
 				722F9A798F39F7D1FE7CF270 /* CodableGeoPointTests.swift in Sources */,
@@ -4322,6 +4335,8 @@
 				E434ACDF63F219F3031F292E /* ConditionalConformanceTests.swift in Sources */,
 				D2A96D452AF6426C491AF931 /* DatabaseTests.swift in Sources */,
 				5B0E2D0595BE30B2320D96F1 /* EncodableFieldValueTests.swift in Sources */,
+				1B9F95F129FAD4D800EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */,
+				EF6C285229E462A200A7D4F1 /* FIRAggregateTests.mm in Sources */,
 				660E99DEDA0A6FC1CCB200F9 /* FIRArrayTransformTests.mm in Sources */,
 				AE068EDBC74AF27679CCB6DA /* FIRBundlesTests.mm in Sources */,
 				BA0BB02821F1949783C8AA50 /* FIRCollectionReferenceTests.mm in Sources */,
@@ -4366,6 +4381,7 @@
 				5E89B1A5A5430713C79C4854 /* FirestoreEncoderTests.swift in Sources */,
 				621D620B28F9CE7400D2FA26 /* QueryIntegrationTests.swift in Sources */,
 				736C4E82689F1CA1859C4A3F /* XCTestCase+Await.mm in Sources */,
+				412BE974741729A6683C386F /* aggregate_query_test.cc in Sources */,
 				DF983A9C1FBF758AF3AF110D /* aggregation_result.pb.cc in Sources */,
 				EA46611779C3EEF12822508C /* annotations.pb.cc in Sources */,
 				8F4F40E9BC7ED588F67734D5 /* app_testing.mm in Sources */,
@@ -4378,7 +4394,6 @@
 				C1B4621C0820EEB0AC9CCD22 /* bits_test.cc in Sources */,
 				01C66732ECCB83AB1D896026 /* bundle.pb.cc in Sources */,
 				EAA1962BFBA0EBFBA53B343F /* bundle_builder.cc in Sources */,
-				1B9F95F82A0933AA00EEC075 /* aggregate_query_test.cc in Sources */,
 				C901A1BFD553B6DD70BB7CC7 /* bundle_cache_test.cc in Sources */,
 				5A44725457D6B7805FD66EEB /* bundle_loader_test.cc in Sources */,
 				248DE4F56DD938F4DBCCF39B /* bundle_reader_test.cc in Sources */,
@@ -4445,7 +4460,6 @@
 				61ECC7CE18700CBD73D0D810 /* leveldb_migrations_test.cc in Sources */,
 				A478FDD7C3F48FBFDDA7D8F5 /* leveldb_mutation_queue_test.cc in Sources */,
 				A06FBB7367CDD496887B86F8 /* leveldb_opener_test.cc in Sources */,
-				1B9F95F129FAD4D800EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */,
 				A9206FF8FF8834347E9C7DDB /* leveldb_overlay_migration_manager_test.cc in Sources */,
 				0E4F266A9FDF55CD38BB6D0F /* leveldb_query_engine_test.cc in Sources */,
 				A27096F764227BC73526FED3 /* leveldb_remote_document_cache_test.cc in Sources */,
@@ -4564,6 +4578,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				1B9F95EE29FAD4CD00EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */,
 				5492E050202154AA00B64F25 /* FIRCollectionReferenceTests.mm in Sources */,
 				5492E053202154AB00B64F25 /* FIRDocumentReferenceTests.mm in Sources */,
 				5492E055202154AB00B64F25 /* FIRDocumentSnapshotTests.mm in Sources */,
@@ -4580,7 +4595,6 @@
 				5492E03E2021401F00B64F25 /* FSTEventAccumulator.mm in Sources */,
 				54764FAF1FAA21B90085E60A /* FSTGoogleTestTests.mm in Sources */,
 				5492E03F2021401F00B64F25 /* FSTHelpers.mm in Sources */,
-				1B9F95F52A0933AA00EEC075 /* aggregate_query_test.cc in Sources */,
 				5491BC721FB44593008B3588 /* FSTIntegrationTestCase.mm in Sources */,
 				5492E03120213FFC00B64F25 /* FSTLevelDBSpecTests.mm in Sources */,
 				5492E03420213FFC00B64F25 /* FSTMemorySpecTests.mm in Sources */,
@@ -4589,6 +4603,7 @@
 				5492E03320213FFC00B64F25 /* FSTSyncEngineTestDriver.mm in Sources */,
 				6FC85C48CF8235BA1845E1C8 /* FSTUserDataReaderTests.mm in Sources */,
 				5492E03C2021401F00B64F25 /* XCTestCase+Await.mm in Sources */,
+				2E7CAC076447970DE881E703 /* aggregate_query_test.cc in Sources */,
 				B81B6F327B5E3FE820DC3FB3 /* aggregation_result.pb.cc in Sources */,
 				618BBEAF20B89AAC00B5BCE7 /* annotations.pb.cc in Sources */,
 				5467FB08203E6A44009C9584 /* app_testing.mm in Sources */,
@@ -4715,7 +4730,6 @@
 				224496E752E42E220F809FAC /* resource.pb.cc in Sources */,
 				B686F2B22025000D0028D6BE /* resource_path_test.cc in Sources */,
 				8A76A3A8345B984C91B0843E /* schedule_test.cc in Sources */,
-				1B9F95EE29FAD4CD00EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */,
 				54740A571FC914BA00713A1A /* secure_random_test.cc in Sources */,
 				61F72C5620BC48FD001A68CB /* serializer_test.cc in Sources */,
 				977E0DA564D6EAF975A4A1A0 /* settings_test.cc in Sources */,
@@ -4785,6 +4799,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				EF6C286D29E6D22200A7D4F1 /* AggregationIntegrationTests.swift in Sources */,
 				062072B72773A055001655D7 /* AsyncAwaitIntegrationTests.swift in Sources */,
 				F731A0CCD0220B370BC1BE8B /* BasicCompileTests.swift in Sources */,
 				7C5E017689012489AAB7718D /* CodableGeoPointTests.swift in Sources */,
@@ -4793,6 +4808,8 @@
 				BCA720A0F54D23654F806323 /* ConditionalConformanceTests.swift in Sources */,
 				CFA4A635ECD105D2044B3692 /* DatabaseTests.swift in Sources */,
 				E688620D4578F1F7FBB1AF9C /* EncodableFieldValueTests.swift in Sources */,
+				1B9F95EF29FAD4CF00EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */,
+				EF6C285129E462A200A7D4F1 /* FIRAggregateTests.mm in Sources */,
 				73866AA12082B0A5009BB4FF /* FIRArrayTransformTests.mm in Sources */,
 				4B54FA587C7107973FD76044 /* FIRBundlesTests.mm in Sources */,
 				7BCC5973C4F4FCC272150E31 /* FIRCollectionReferenceTests.mm in Sources */,
@@ -4837,6 +4854,7 @@
 				6F45846C159D3C063DBD3CBE /* FirestoreEncoderTests.swift in Sources */,
 				621D620A28F9CE7400D2FA26 /* QueryIntegrationTests.swift in Sources */,
 				5492E0442021457E00B64F25 /* XCTestCase+Await.mm in Sources */,
+				B04E4FE20930384DF3A402F9 /* aggregate_query_test.cc in Sources */,
 				1A3D8028303B45FCBB21CAD3 /* aggregation_result.pb.cc in Sources */,
 				02EB33CC2590E1484D462912 /* annotations.pb.cc in Sources */,
 				EBFC611B1BF195D0EC710AF4 /* app_testing.mm in Sources */,
@@ -4849,7 +4867,6 @@
 				0B9BD73418289EFF91917934 /* bits_test.cc in Sources */,
 				F8126CD7308A4B8AEC0F30A8 /* bundle.pb.cc in Sources */,
 				5AFA1055E8F6B4E4B1CCE2C4 /* bundle_builder.cc in Sources */,
-				1B9F95F62A0933AA00EEC075 /* aggregate_query_test.cc in Sources */,
 				AE5E5E4A7BF12C2337AFA13B /* bundle_cache_test.cc in Sources */,
 				65D54B964A2021E5A36AB21F /* bundle_loader_test.cc in Sources */,
 				B9706A5CD29195A613CF4147 /* bundle_reader_test.cc in Sources */,
@@ -4916,7 +4933,6 @@
 				B576823475FBCA5EFA583F9C /* leveldb_migrations_test.cc in Sources */,
 				4FAD8823DC37B9CA24379E85 /* leveldb_mutation_queue_test.cc in Sources */,
 				4562CDD90F5FF0491F07C5DA /* leveldb_opener_test.cc in Sources */,
-				1B9F95EF29FAD4CF00EEC075 /* FIRAggregateQueryUnitTests.mm in Sources */,
 				1A1299107EFF68DA9DAB19BD /* leveldb_overlay_migration_manager_test.cc in Sources */,
 				4ADBF70036448B1395DC5657 /* leveldb_query_engine_test.cc in Sources */,
 				EE6DBFB0874A50578CE97A7F /* leveldb_remote_document_cache_test.cc in Sources */,

+ 8 - 0
Firestore/Example/Tests/API/FSTAPIHelpers.h

@@ -73,4 +73,12 @@ FIRQuerySnapshot *FSTTestQuerySnapshot(
 }  // extern "C"
 #endif
 
+@interface FSTNSExceptionUtil : NSObject
+
++ (BOOL)testForException:(void (^)(void))methodToTry
+          reasonContains:(nonnull NSString *)message
+    NS_SWIFT_NAME(testForException(_:reasonContains:));
+
+@end
+
 NS_ASSUME_NONNULL_END

+ 14 - 0
Firestore/Example/Tests/API/FSTAPIHelpers.mm

@@ -163,4 +163,18 @@ FIRQuerySnapshot *FSTTestQuerySnapshot(
                                             metadata:std::move(metadata)];
 }
 
+@implementation FSTNSExceptionUtil
+
++ (BOOL)testForException:(nonnull void (^)())methodToTry
+          reasonContains:(nonnull NSString *)substring {
+  @try {
+    methodToTry();
+    return NO;
+  } @catch (NSException *exception) {
+    return [[exception reason] containsString:substring];
+  }
+}
+
+@end
+
 NS_ASSUME_NONNULL_END

+ 1445 - 0
Firestore/Example/Tests/Integration/API/FIRAggregateTests.mm

@@ -0,0 +1,1445 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <FirebaseFirestore/FIRFieldPath.h>
+#import <FirebaseFirestore/FirebaseFirestore.h>
+
+#import <XCTest/XCTest.h>
+
+// TODO(sum/avg) update these imports with public imports when sum/avg is public
+#import "Firestore/Source/API/FIRAggregateField.h"
+#import "Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h"
+#import "Firestore/Source/API/FIRQuery+Internal.h"
+
+#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
+
+@interface FIRAggregateTests : FSTIntegrationTestCase
+@end
+
+@implementation FIRAggregateTests
+
+- (void)testCountAggregateFieldQueryEquals {
+  FIRCollectionReference* coll1 = [self collectionRefWithDocuments:@{}];
+  FIRCollectionReference* coll1Same = [[coll1 firestore] collectionWithPath:[coll1 path]];
+
+  FIRAggregateQuery* query1 = [coll1 aggregate:@[ [FIRAggregateField aggregateFieldForCount] ]];
+  FIRAggregateQuery* query1Same =
+      [coll1Same aggregate:@[ [FIRAggregateField aggregateFieldForCount] ]];
+  FIRAggregateQuery* query1DiffAgg =
+      [coll1Same aggregate:@[ [FIRAggregateField aggregateFieldForSumOfField:@"baz"] ]];
+
+  FIRCollectionReference* sub = [[coll1 documentWithPath:@"bar"] collectionWithPath:@"baz"];
+
+  FIRAggregateQuery* query2 = [[[sub queryWhereField:@"a" isEqualTo:@1] queryLimitedTo:100]
+      aggregate:@[ [FIRAggregateField aggregateFieldForCount] ]];
+  FIRAggregateQuery* query2Same = [[[sub queryWhereField:@"a"
+                                               isEqualTo:@1] queryLimitedTo:100] count];
+
+  FIRAggregateQuery* query3 = [[[sub queryWhereField:@"b" isEqualTo:@1] queryOrderedByField:@"c"]
+      aggregate:@[ [FIRAggregateField aggregateFieldForCount] ]];
+  FIRAggregateQuery* query3Same = [[[sub queryWhereField:@"b"
+                                               isEqualTo:@1] queryOrderedByField:@"c"] count];
+
+  XCTAssertEqualObjects(query1, query1Same);
+  XCTAssertEqualObjects(query2, query2Same);
+  XCTAssertEqualObjects(query3, query3Same);
+
+  XCTAssertEqual([query1 hash], [query1Same hash]);
+  XCTAssertEqual([query2 hash], [query2Same hash]);
+  XCTAssertEqual([query3 hash], [query3Same hash]);
+
+  XCTAssertFalse([query1 isEqual:nil]);
+  XCTAssertFalse([query1 isEqual:@"string"]);
+  XCTAssertFalse([query1 isEqual:query2]);
+  XCTAssertFalse([query2 isEqual:query3]);
+
+  XCTAssertNotEqual([query1 hash], [query1DiffAgg hash]);
+  XCTAssertNotEqual([query1 hash], [query2 hash]);
+  XCTAssertNotEqual([query2 hash], [query3 hash]);
+}
+
+- (void)testSumAggregateFieldQueryEquals {
+  FIRCollectionReference* coll1 = [self collectionRefWithDocuments:@{}];
+  FIRCollectionReference* coll1Same = [[coll1 firestore] collectionWithPath:[coll1 path]];
+
+  FIRAggregateQuery* query1 =
+      [coll1 aggregate:@[ [FIRAggregateField aggregateFieldForSumOfField:@"baz"] ]];
+  FIRAggregateQuery* query1Same =
+      [coll1Same aggregate:@[ [FIRAggregateField aggregateFieldForSumOfField:@"baz"] ]];
+  FIRAggregateQuery* query1WithFieldPath =
+      [coll1Same aggregate:@[ [FIRAggregateField
+                               aggregateFieldForSumOfFieldPath:[[FIRFieldPath alloc]
+                                                                   initWithFields:@[ @"baz" ]]] ]];
+  FIRAggregateQuery* query1DiffAgg =
+      [coll1Same aggregate:@[ [FIRAggregateField aggregateFieldForCount] ]];
+
+  FIRCollectionReference* sub = [[coll1 documentWithPath:@"bar"] collectionWithPath:@"baz"];
+  FIRAggregateQuery* query2 = [[[sub queryWhereField:@"a" isEqualTo:@1] queryLimitedTo:100]
+      aggregate:@[ [FIRAggregateField aggregateFieldForSumOfField:@"baz"] ]];
+  FIRAggregateQuery* query2Same = [[[sub queryWhereField:@"a" isEqualTo:@1] queryLimitedTo:100]
+      aggregate:@[ [FIRAggregateField aggregateFieldForSumOfField:@"baz"] ]];
+  FIRAggregateQuery* query3 = [[[sub queryWhereField:@"b" isEqualTo:@1] queryOrderedByField:@"c"]
+      aggregate:@[ [FIRAggregateField aggregateFieldForSumOfField:@"baz"] ]];
+  FIRAggregateQuery* query3Same = [[[sub queryWhereField:@"b"
+                                               isEqualTo:@1] queryOrderedByField:@"c"]
+      aggregate:@[ [FIRAggregateField aggregateFieldForSumOfField:@"baz"] ]];
+
+  XCTAssertEqualObjects(query1, query1Same);
+  XCTAssertEqualObjects(query1, query1WithFieldPath);
+  XCTAssertEqualObjects(query2, query2Same);
+  XCTAssertEqualObjects(query3, query3Same);
+
+  XCTAssertEqual([query1 hash], [query1Same hash]);
+  XCTAssertEqual([query1 hash], [query1WithFieldPath hash]);
+  XCTAssertEqual([query2 hash], [query2Same hash]);
+  XCTAssertEqual([query3 hash], [query3Same hash]);
+
+  XCTAssertFalse([query1 isEqual:nil]);
+  XCTAssertFalse([query1 isEqual:@"string"]);
+  XCTAssertFalse([query1 isEqual:query2]);
+  XCTAssertFalse([query2 isEqual:query3]);
+
+  XCTAssertNotEqual([query1 hash], [query1DiffAgg hash]);
+  XCTAssertNotEqual([query1 hash], [query2 hash]);
+  XCTAssertNotEqual([query2 hash], [query3 hash]);
+}
+
+- (void)testAverageAggregateFieldQueryEquals {
+  FIRCollectionReference* coll1 = [self collectionRefWithDocuments:@{}];
+  FIRCollectionReference* coll1Same = [[coll1 firestore] collectionWithPath:[coll1 path]];
+
+  FIRAggregateQuery* query1 =
+      [coll1 aggregate:@[ [FIRAggregateField aggregateFieldForAverageOfField:@"baz"] ]];
+  FIRAggregateQuery* query1Same =
+      [coll1Same aggregate:@[ [FIRAggregateField aggregateFieldForAverageOfField:@"baz"] ]];
+  FIRAggregateQuery* query1WithFieldPath = [coll1Same aggregate:@[
+    [FIRAggregateField
+        aggregateFieldForAverageOfFieldPath:[[FIRFieldPath alloc] initWithFields:@[ @"baz" ]]]
+  ]];
+  FIRAggregateQuery* query1DiffAgg =
+      [coll1Same aggregate:@[ [FIRAggregateField aggregateFieldForCount] ]];
+
+  FIRCollectionReference* sub = [[coll1 documentWithPath:@"bar"] collectionWithPath:@"baz"];
+
+  FIRAggregateQuery* query2 = [[[sub queryWhereField:@"a" isEqualTo:@1] queryLimitedTo:100]
+      aggregate:@[ [FIRAggregateField aggregateFieldForAverageOfField:@"baz"] ]];
+  FIRAggregateQuery* query2Same = [[[sub queryWhereField:@"a" isEqualTo:@1] queryLimitedTo:100]
+      aggregate:@[ [FIRAggregateField aggregateFieldForAverageOfField:@"baz"] ]];
+
+  FIRAggregateQuery* query3 = [[[sub queryWhereField:@"b" isEqualTo:@1] queryOrderedByField:@"c"]
+      aggregate:@[ [FIRAggregateField aggregateFieldForAverageOfField:@"baz"] ]];
+  FIRAggregateQuery* query3Same = [[[sub queryWhereField:@"b"
+                                               isEqualTo:@1] queryOrderedByField:@"c"]
+      aggregate:@[ [FIRAggregateField aggregateFieldForAverageOfField:@"baz"] ]];
+
+  XCTAssertEqualObjects(query1, query1Same);
+  XCTAssertEqualObjects(query1, query1WithFieldPath);
+  XCTAssertEqualObjects(query2, query2Same);
+  XCTAssertEqualObjects(query3, query3Same);
+
+  XCTAssertEqual([query1 hash], [query1Same hash]);
+  XCTAssertEqual([query1 hash], [query1WithFieldPath hash]);
+  XCTAssertEqual([query2 hash], [query2Same hash]);
+  XCTAssertEqual([query3 hash], [query3Same hash]);
+
+  XCTAssertFalse([query1 isEqual:nil]);
+  XCTAssertFalse([query1 isEqual:@"string"]);
+  XCTAssertFalse([query1 isEqual:query2]);
+  XCTAssertFalse([query2 isEqual:query3]);
+
+  XCTAssertNotEqual([query1 hash], [query1DiffAgg hash]);
+  XCTAssertNotEqual([query1 hash], [query2 hash]);
+  XCTAssertNotEqual([query2 hash], [query3 hash]);
+}
+
+- (void)testAggregateFieldQueryNotEquals {
+  FIRCollectionReference* coll = [self collectionRefWithDocuments:@{}];
+
+  FIRAggregateQuery* query1 = [coll aggregate:@[ [FIRAggregateField aggregateFieldForCount] ]];
+  FIRAggregateQuery* query2 =
+      [coll aggregate:@[ [FIRAggregateField aggregateFieldForSumOfField:@"baz"] ]];
+  FIRAggregateQuery* query3 =
+      [coll aggregate:@[ [FIRAggregateField aggregateFieldForAverageOfField:@"baz"] ]];
+
+  XCTAssertNotEqualObjects(query1, query2);
+  XCTAssertNotEqualObjects(query2, query3);
+  XCTAssertNotEqualObjects(query3, query1);
+
+  XCTAssertNotEqual([query1 hash], [query2 hash]);
+  XCTAssertNotEqual([query2 hash], [query3 hash]);
+  XCTAssertNotEqual([query3 hash], [query1 hash]);
+
+  FIRQuery* baseQuery = [[[[coll documentWithPath:@"bar"] collectionWithPath:@"baz"]
+      queryWhereField:@"a"
+            isEqualTo:@1] queryLimitedTo:100];
+
+  FIRAggregateQuery* query4 = [baseQuery aggregate:@[ [FIRAggregateField aggregateFieldForCount] ]];
+  FIRAggregateQuery* query5 =
+      [baseQuery aggregate:@[ [FIRAggregateField aggregateFieldForSumOfField:@"baz"] ]];
+  FIRAggregateQuery* query6 =
+      [baseQuery aggregate:@[ [FIRAggregateField aggregateFieldForAverageOfField:@"baz"] ]];
+
+  XCTAssertNotEqualObjects(query4, query5);
+  XCTAssertNotEqualObjects(query5, query6);
+  XCTAssertNotEqualObjects(query6, query4);
+
+  XCTAssertNotEqual([query4 hash], [query5 hash]);
+  XCTAssertNotEqual([query5 hash], [query6 hash]);
+  XCTAssertNotEqual([query6 hash], [query4 hash]);
+}
+
+- (void)testCanRunAggregateQuery {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"height" : @24.5,
+      @"weight" : @24.1,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"height" : @25.5,
+      @"weight" : @75.5,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    }
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForCount],
+              [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
+              [FIRAggregateField aggregateFieldForSumOfField:@"weight"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"pages"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"weight"]
+            ]]];
+
+  // Count
+  XCTAssertEqual([snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]],
+                 [NSNumber numberWithLong:2L]);
+  XCTAssertEqual([snapshot count], [NSNumber numberWithLong:2L]);
+
+  // Sum
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]],
+      [NSNumber numberWithLong:150L], );
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"weight"]]
+          doubleValue],
+      99.6);
+
+  // Average
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]],
+      [NSNumber numberWithDouble:75.0]);
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"weight"]]
+          doubleValue],
+      49.8);
+}
+
+- (void)testCanRunEmptyAggregateQuery {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"height" : @24.5,
+      @"weight" : @24.1,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"height" : @25.5,
+      @"weight" : @75.5,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    }
+  }];
+
+  FIRAggregateQuery* emptyQuery = [testCollection aggregate:@[]];
+
+  __block NSError* result;
+  XCTestExpectation* expectation = [self expectationWithDescription:@"aggregate result"];
+
+  [emptyQuery aggregationWithSource:FIRAggregateSourceServer
+                         completion:^(FIRAggregateQuerySnapshot* snapshot, NSError* error) {
+                           XCTAssertNil(snapshot);
+                           result = error;
+                           [expectation fulfill];
+                         }];
+
+  [self awaitExpectation:expectation];
+
+  XCTAssertNotNil(result);
+  XCTAssertTrue([[result localizedDescription] containsString:@"Aggregations can not be empty"]);
+}
+
+- (void)testAggregateFieldQuerySnapshotEquality {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"height" : @24.5,
+      @"weight" : @24.1,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"height" : @25.5,
+      @"weight" : @75.5,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    }
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot1 =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForCount],
+              [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
+              [FIRAggregateField aggregateFieldForSumOfField:@"weight"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"pages"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"weight"]
+            ]]];
+
+  FIRAggregateQuerySnapshot* snapshot2 =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForCount],
+              [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
+              [FIRAggregateField aggregateFieldForSumOfField:@"weight"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"pages"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"weight"]
+            ]]];
+
+  // different aggregates
+  FIRAggregateQuerySnapshot* snapshot3 =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForCount],
+              [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
+              [FIRAggregateField aggregateFieldForSumOfField:@"weight"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"pages"]
+            ]]];
+
+  // different data set
+  FIRAggregateQuerySnapshot* snapshot4 = [self
+      readSnapshotForAggregate:[[testCollection queryWhereField:@"pages" isGreaterThan:@50]
+                                   aggregate:@[
+                                     [FIRAggregateField aggregateFieldForCount],
+                                     [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
+                                     [FIRAggregateField aggregateFieldForSumOfField:@"weight"],
+                                     [FIRAggregateField aggregateFieldForAverageOfField:@"pages"],
+                                     [FIRAggregateField
+                                         aggregateFieldForAverageOfField:@"weight"]
+                                   ]]];
+
+  XCTAssertEqualObjects(snapshot1, snapshot2);
+  XCTAssertNotEqualObjects(snapshot1, snapshot3);
+  XCTAssertNotEqualObjects(snapshot1, snapshot4);
+  XCTAssertNotEqualObjects(snapshot3, snapshot4);
+
+  XCTAssertEqual([snapshot1 hash], [snapshot2 hash]);
+  XCTAssertNotEqual([snapshot1 hash], [snapshot3 hash]);
+  XCTAssertNotEqual([snapshot1 hash], [snapshot4 hash]);
+  XCTAssertNotEqual([snapshot3 hash], [snapshot4 hash]);
+}
+
+- (void)testAllowsAliasesLongerThan1500Bytes {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  // The longest field name allowed is 1500. The alias chosen by the client is <op>_<fieldName>.
+  // If the field name is
+  // 1500 bytes, the alias will be longer than 1500, which is the limit for aliases. This is to
+  // make sure the client
+  // can handle this corner case correctly.
+  NSString* longField = [@"" stringByPaddingToLength:1499
+                                          withString:@"0123456789"
+                                     startingAtIndex:0];
+
+  FIRCollectionReference* testCollection =
+      [self collectionRefWithDocuments:@{@"a" : @{longField : @1}, @"b" : @{longField : @2}}];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection
+                                         aggregate:@[ [FIRAggregateField
+                                                       aggregateFieldForSumOfField:longField] ]]];
+
+  // Sum
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:longField]],
+      [NSNumber numberWithLong:3], );
+}
+
+- (void)testCanGetDuplicateAggregations {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"height" : @24.5,
+      @"weight" : @24.1,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"height" : @25.5,
+      @"weight" : @75.5,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    }
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForCount],
+              [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
+              [FIRAggregateField aggregateFieldForSumOfField:@"pages"]
+            ]]];
+
+  // Count
+  XCTAssertEqual([snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]],
+                 [NSNumber numberWithLong:2L]);
+
+  // Sum
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]],
+      [NSNumber numberWithLong:150L], );
+}
+
+- (void)testTerminateDoesNotCrashWithFlyingAggregateQuery {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"height" : @24.5,
+      @"weight" : @24.1,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"height" : @25.5,
+      @"weight" : @75.5,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    }
+  }];
+
+  FIRAggregateQuery* aggQuery = [testCollection aggregate:@[
+    [FIRAggregateField aggregateFieldForCount],
+    [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
+    [FIRAggregateField aggregateFieldForAverageOfField:@"pages"]
+  ]];
+
+  __block FIRAggregateQuerySnapshot* result;
+  XCTestExpectation* expectation = [self expectationWithDescription:@"aggregate result"];
+  [aggQuery aggregationWithSource:FIRAggregateSourceServer
+                       completion:^(FIRAggregateQuerySnapshot* snapshot, NSError* error) {
+                         XCTAssertNil(error);
+                         result = snapshot;
+                         [expectation fulfill];
+                       }];
+
+  [self awaitExpectation:expectation];
+
+  // Count
+  XCTAssertEqual([result valueForAggregation:[FIRAggregateField aggregateFieldForCount]],
+                 [NSNumber numberWithLong:2L]);
+
+  // Sum
+  XCTAssertEqual(
+      [result valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]],
+      [NSNumber numberWithLong:150L], );
+}
+
+- (void)testCanPerformMaxAggregations {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"height" : @24.5,
+      @"weight" : @24.1,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"height" : @25.5,
+      @"weight" : @75.5,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    }
+  }];
+
+  // Max is 5, do not exceed
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForCount],
+              [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
+              [FIRAggregateField aggregateFieldForSumOfField:@"weight"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"pages"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"weight"]
+            ]]];
+
+  // Assert
+  XCTAssertEqual([snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]],
+                 [NSNumber numberWithLong:2L]);
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]],
+      [NSNumber numberWithLong:150L], );
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"weight"]]
+          doubleValue],
+      99.6);
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]],
+      [NSNumber numberWithDouble:75.0]);
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"weight"]]
+          doubleValue],
+      49.8);
+}
+
+- (void)testCannotPerformMoreThanMaxAggregations {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"height" : @24.5,
+      @"weight" : @24.1,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"height" : @25.5,
+      @"weight" : @75.5,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    }
+  }];
+
+  // Max is 5, we're attempting 6. I also like to live dangerously.
+  FIRAggregateQuery* query = [testCollection aggregate:@[
+    [FIRAggregateField aggregateFieldForCount],
+    [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
+    [FIRAggregateField aggregateFieldForSumOfField:@"weight"],
+    [FIRAggregateField aggregateFieldForAverageOfField:@"pages"],
+    [FIRAggregateField aggregateFieldForAverageOfField:@"weight"],
+    [FIRAggregateField aggregateFieldForAverageOfField:@"foo"]
+  ]];
+
+  __block NSError* result;
+  XCTestExpectation* expectation = [self expectationWithDescription:@"aggregate result"];
+
+  [query aggregationWithSource:FIRAggregateSourceServer
+                    completion:^(FIRAggregateQuerySnapshot* snapshot, NSError* error) {
+                      XCTAssertNil(snapshot);
+                      result = error;
+                      [expectation fulfill];
+                    }];
+
+  [self awaitExpectation:expectation];
+
+  XCTAssertNotNil(result);
+  XCTAssertTrue([[result localizedDescription] containsString:@"maximum number of aggregations"]);
+}
+
+- (void)testCanRunAggregateCollectionGroupQuery {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  NSString* collectionGroup =
+      [NSString stringWithFormat:@"%@%@", @"b",
+                                 [self.db collectionWithPath:@"foo"].documentWithAutoID.documentID];
+  NSArray* docPathFormats = @[
+    @"abc/123/%@/cg-doc1", @"abc/123/%@/cg-doc2", @"%@/cg-doc3", @"%@/cg-doc4",
+    @"def/456/%@/cg-doc5", @"%@/virtual-doc/nested-coll/not-cg-doc", @"x%@/not-cg-doc",
+    @"%@x/not-cg-doc", @"abc/123/%@x/not-cg-doc", @"abc/123/x%@/not-cg-doc", @"abc/%@"
+  ];
+
+  FIRWriteBatch* batch = self.db.batch;
+  for (NSString* format in docPathFormats) {
+    NSString* path = [NSString stringWithFormat:format, collectionGroup];
+    [batch setData:@{@"x" : @2} forDocument:[self.db documentWithPath:path]];
+  }
+
+  XCTestExpectation* expectation = [self expectationWithDescription:@"commit"];
+  [batch commitWithCompletion:^(NSError* error) {
+    XCTAssertNil(error);
+    [expectation fulfill];
+  }];
+  [self awaitExpectation:expectation];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[[self.db collectionGroupWithID:collectionGroup] aggregate:@[
+              [FIRAggregateField aggregateFieldForCount],
+              [FIRAggregateField aggregateFieldForSumOfField:@"x"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"x"]
+            ]]];
+  // "cg-doc1", "cg-doc2", "cg-doc3", "cg-doc4", "cg-doc5",
+  XCTAssertEqual([snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]],
+                 [NSNumber numberWithLong:5L]);
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"x"]],
+      [NSNumber numberWithLong:10L]);
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"x"]],
+      [NSNumber numberWithDouble:2.0]);
+}
+
+- (void)testPerformsAggregationsWhenNaNExistsForSomeFieldValues {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"year" : @1980,
+      @"rating" : @5
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"year" : @2020,
+      @"rating" : @4
+    },
+    @"c" : @{
+      @"author" : @"authorC",
+      @"title" : @"titleC",
+      @"pages" : @100,
+      @"year" : @1980,
+      @"rating" : [NSNumber numberWithFloat:NAN]
+    },
+    @"d" : @{
+      @"author" : @"authorD",
+      @"title" : @"titleD",
+      @"pages" : @50,
+      @"year" : @2020,
+      @"rating" : @0
+    }
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForSumOfField:@"rating"],
+              [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"rating"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"year"]
+            ]]];
+
+  // Sum
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]],
+      [NSNumber numberWithDouble:NAN]);
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]]
+          longValue],
+      300L);
+
+  // Average
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]],
+      [NSNumber numberWithDouble:NAN]);
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"year"]]
+          doubleValue],
+      2000.0);
+}
+
+- (void)testThrowsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"height" : @24.5,
+      @"weight" : @24.1,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"height" : @25.5,
+      @"weight" : @75.5,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    }
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection
+                                         aggregate:@[ [FIRAggregateField
+                                                       aggregateFieldForSumOfField:@"pages"] ]]];
+
+  @try {
+    [snapshot count];
+    XCTAssertTrue(false, "Exception expected");
+  } @catch (NSException* exception) {
+    XCTAssertEqualObjects(exception.name, @"FIRInvalidArgumentException");
+    XCTAssertEqualObjects(exception.reason,
+                          @"'count()' was not requested in the aggregation query.");
+  }
+
+  @
+  try {
+    [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"foo"]];
+    XCTAssertTrue(false, "Exception expected");
+  } @catch (NSException* exception) {
+    XCTAssertEqualObjects(exception.name, @"FIRInvalidArgumentException");
+    XCTAssertEqualObjects(exception.reason,
+                          @"'sum(foo)' was not requested in the aggregation query.");
+  }
+
+  @
+  try {
+    [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]];
+    XCTAssertTrue(false, "Exception expected");
+  } @catch (NSException* exception) {
+    XCTAssertEqualObjects(exception.name, @"FIRInvalidArgumentException");
+    XCTAssertEqualObjects(exception.reason,
+                          @"'avg(pages)' was not requested in the aggregation query.");
+  }
+}
+
+- (void)testPerformsAggregationWhenUsingInOperator {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"year" : @1980,
+      @"rating" : @5
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"year" : @2020,
+      @"rating" : @4
+    },
+    @"c" : @{
+      @"author" : @"authorC",
+      @"title" : @"titleC",
+      @"pages" : @100,
+      @"year" : @1980,
+      @"rating" : @3
+    },
+    @"d" : @{
+      @"author" : @"authorD",
+      @"title" : @"titleD",
+      @"pages" : @50,
+      @"year" : @2020,
+      @"rating" : @0
+    }
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot = [self
+      readSnapshotForAggregate:[[testCollection queryWhereField:@"rating" in:@[ @5, @3 ]]
+                                   aggregate:@[
+                                     [FIRAggregateField aggregateFieldForSumOfField:@"rating"],
+                                     [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
+                                     [FIRAggregateField aggregateFieldForAverageOfField:@"rating"],
+                                     [FIRAggregateField aggregateFieldForAverageOfField:@"pages"],
+                                     [FIRAggregateField aggregateFieldForCount]
+                                   ]]];
+
+  // Count
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]] longValue], 2L);
+
+  // Sum
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]]
+          longValue],
+      8L);
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]]
+          longValue],
+      200L);
+
+  // Average
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]]
+          doubleValue],
+      4.0);
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]]
+          doubleValue],
+      100.0);
+}
+
+- (void)testPerformsAggregationWhenUsingArrayContainsAnyOperator {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"year" : @1980,
+      @"rating" : @[ @5, @1000 ]
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"year" : @2020,
+      @"rating" : @[ @4 ]
+    },
+    @"c" : @{
+      @"author" : @"authorC",
+      @"title" : @"titleC",
+      @"pages" : @100,
+      @"year" : @1980,
+      @"rating" : @[ @2222, @3 ]
+    },
+    @"d" : @{
+      @"author" : @"authorD",
+      @"title" : @"titleD",
+      @"pages" : @50,
+      @"year" : @2020,
+      @"rating" : @[ @0 ]
+    }
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot = [self
+      readSnapshotForAggregate:[[testCollection queryWhereField:@"rating"
+                                               arrayContainsAny:@[ @5, @3 ]]
+                                   aggregate:@[
+                                     [FIRAggregateField aggregateFieldForSumOfField:@"rating"],
+                                     [FIRAggregateField aggregateFieldForSumOfField:@"pages"],
+                                     [FIRAggregateField aggregateFieldForAverageOfField:@"rating"],
+                                     [FIRAggregateField aggregateFieldForAverageOfField:@"pages"],
+                                     [FIRAggregateField aggregateFieldForCount]
+                                   ]]];
+
+  // Count
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]] longValue], 2L);
+
+  // Sum
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]]
+          longValue],
+      0L);
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]]
+          longValue],
+      200L);
+
+  // Average
+  XCTAssertEqualObjects(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]],
+      [NSNull null]);
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]]
+          doubleValue],
+      100.0);
+}
+
+- (void)testPerformsAggregationsOnNestedMapValues {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"metadata" : @{@"pages" : @100, @"rating" : @{@"critic" : @2, @"user" : @5}}
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"metadata" : @{@"pages" : @50, @"rating" : @{@"critic" : @4, @"user" : @4}}
+    },
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForSumOfField:@"metadata.pages"],
+              [FIRAggregateField aggregateFieldForSumOfField:@"metadata.rating.user"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"metadata.pages"],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"metadata.rating.critic"],
+              [FIRAggregateField aggregateFieldForCount]
+            ]]];
+
+  // Count
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]] longValue], 2L);
+
+  // Sum
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField
+                                         aggregateFieldForSumOfField:@"metadata.pages"]] longValue],
+      150L);
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField
+                                         aggregateFieldForSumOfField:@"metadata.rating.user"]]
+          longValue],
+      9);
+
+  // Average
+  XCTAssertEqual(
+      [[snapshot
+          valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"metadata.pages"]]
+          doubleValue],
+      75.0);
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField
+                                         aggregateFieldForAverageOfField:@"metadata.rating.critic"]]
+          doubleValue],
+      3.0);
+}
+
+- (void)testPerformsSumThatOverflowsMaxLong {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"rating" : [NSNumber numberWithLong:LLONG_MAX]
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"rating" : [NSNumber numberWithLong:LLONG_MAX]
+    },
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection
+                                         aggregate:@[ [FIRAggregateField
+                                                       aggregateFieldForSumOfField:@"rating"] ]]];
+
+  // Sum
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]]
+          doubleValue],
+      [[NSNumber numberWithLong:LLONG_MAX] doubleValue] +
+          [[NSNumber numberWithLong:LLONG_MAX] doubleValue]);
+}
+
+- (void)testPerformsSumThatCanOverflowLongValuesDuringAccumulation {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"rating" : [NSNumber numberWithLong:LLONG_MAX]
+    },
+    @"b" : @{@"author" : @"authorB", @"title" : @"titleB", @"rating" : [NSNumber numberWithLong:1]},
+    @"c" :
+        @{@"author" : @"authorC", @"title" : @"titleC", @"rating" : [NSNumber numberWithLong:-101]}
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection
+                                         aggregate:@[ [FIRAggregateField
+                                                       aggregateFieldForSumOfField:@"rating"] ]]];
+
+  // Sum
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]]
+          longLongValue],
+      [[NSNumber numberWithLong:LLONG_MAX - 100] longLongValue]);
+}
+
+- (void)testPerformsSumThatIsNegative {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"rating" : [NSNumber numberWithLong:LLONG_MAX]
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"rating" : [NSNumber numberWithLong:-LLONG_MAX]
+    },
+    @"c" :
+        @{@"author" : @"authorC", @"title" : @"titleC", @"rating" : [NSNumber numberWithLong:-101]},
+    @"d" : @{
+      @"author" : @"authorD",
+      @"title" : @"titleD",
+      @"rating" : [NSNumber numberWithLong:-10000]
+    }
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection
+                                         aggregate:@[ [FIRAggregateField
+                                                       aggregateFieldForSumOfField:@"rating"] ]]];
+
+  // Sum
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]]
+          longLongValue],
+      [[NSNumber numberWithLong:-10101] longLongValue]);
+}
+
+- (void)testPerformsSumThatIsPositiveInfinity {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"rating" : [NSNumber numberWithDouble:DBL_MAX]
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"rating" : [NSNumber numberWithDouble:DBL_MAX]
+    }
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection
+                                         aggregate:@[ [FIRAggregateField
+                                                       aggregateFieldForSumOfField:@"rating"] ]]];
+
+  // Sum
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]],
+      [NSNumber numberWithDouble:INFINITY]);
+}
+
+- (void)testPerformsSumThatIsNegativeInfinity {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"rating" : [NSNumber numberWithDouble:-DBL_MAX]
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"rating" : [NSNumber numberWithDouble:-DBL_MAX]
+    }
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection
+                                         aggregate:@[ [FIRAggregateField
+                                                       aggregateFieldForSumOfField:@"rating"] ]]];
+
+  // Sum
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]],
+      [NSNumber numberWithDouble:-INFINITY]);
+}
+
+- (void)testPerformsSumThatIsValidButCouldOverflowDuringAggregation {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{@"rating" : [NSNumber numberWithDouble:DBL_MAX]},
+    @"b" : @{@"rating" : [NSNumber numberWithDouble:DBL_MAX]},
+    @"c" : @{@"rating" : [NSNumber numberWithDouble:-DBL_MAX]},
+    @"d" : @{@"rating" : [NSNumber numberWithDouble:-DBL_MAX]},
+    @"e" : @{@"rating" : [NSNumber numberWithDouble:DBL_MAX]},
+    @"f" : @{@"rating" : [NSNumber numberWithDouble:-DBL_MAX]},
+    @"g" : @{@"rating" : [NSNumber numberWithDouble:-DBL_MAX]},
+    @"h" : @{@"rating" : [NSNumber numberWithDouble:DBL_MAX]}
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection
+                                         aggregate:@[ [FIRAggregateField
+                                                       aggregateFieldForSumOfField:@"rating"] ]]];
+
+  // Sum
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]]
+          longLongValue],
+      [[NSNumber numberWithLong:0] longLongValue]);
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]]
+          doubleValue],
+      [[NSNumber numberWithLong:0] doubleValue]);
+}
+
+- (void)testPerformsSumOverResultSetOfZeroDocuments {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"height" : @24.5,
+      @"weight" : @24.1,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"height" : @25.5,
+      @"weight" : @75.5,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    }
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[[testCollection queryWhereField:@"pages" isGreaterThan:@200]
+                                         aggregate:@[ [FIRAggregateField
+                                                       aggregateFieldForSumOfField:@"pages"] ]]];
+
+  // Sum
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"pages"]],
+      [NSNumber numberWithLong:0L]);
+}
+
+- (void)testPerformsSumOnlyOnNumericFields {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{@"rating" : [NSNumber numberWithLong:5]},
+    @"b" : @{@"rating" : [NSNumber numberWithLong:4]},
+    @"c" : @{@"rating" : @"3"},
+    @"d" : @{@"rating" : [NSNumber numberWithLong:1]}
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForCount],
+              [FIRAggregateField aggregateFieldForSumOfField:@"rating"]
+            ]]];
+
+  // Count
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]] longValue], 4L);
+
+  // Sum
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]]
+          longLongValue],
+      [[NSNumber numberWithLong:10] longLongValue]);
+}
+
+- (void)testPerformsSumOfMinIEEE754 {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{@"rating" : [NSNumber numberWithDouble:__DBL_DENORM_MIN__]}
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection
+                                         aggregate:@[ [FIRAggregateField
+                                                       aggregateFieldForSumOfField:@"rating"] ]]];
+
+  // Sum
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForSumOfField:@"rating"]]
+          doubleValue],
+      [[NSNumber numberWithDouble:__DBL_DENORM_MIN__] doubleValue]);
+}
+
+- (void)testPerformsAverageOfVariousNumericTypes {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"x" : @1,
+      @"intToInt" : [NSNumber numberWithLong:10],
+      @"floatToInt" : [NSNumber numberWithDouble:10.5],
+      @"mixedToInt" : [NSNumber numberWithLong:10],
+      @"floatToFloat" : [NSNumber numberWithDouble:5.5],
+      @"mixedToFloat" : [NSNumber numberWithDouble:8.6],
+      @"intToFloat" : [NSNumber numberWithLong:10]
+    },
+    @"b" : @{
+      @"intToInt" : [NSNumber numberWithLong:5],
+      @"floatToInt" : [NSNumber numberWithDouble:9.5],
+      @"mixedToInt" : [NSNumber numberWithDouble:9.5],
+      @"floatToFloat" : [NSNumber numberWithDouble:4.5],
+      @"mixedToFloat" : [NSNumber numberWithLong:9],
+      @"intToFloat" : [NSNumber numberWithLong:9]
+    },
+    @"c" : @{
+      @"intToInt" : [NSNumber numberWithLong:0],
+      @"floatToInt" : @"ignore",
+      @"mixedToInt" : [NSNumber numberWithDouble:10.5],
+      @"floatToFloat" : [NSNumber numberWithDouble:3.5],
+      @"mixedToFloat" : [NSNumber numberWithLong:10],
+      @"intToFloat" : @"ignore"
+    }
+  }];
+
+  NSArray* testCases = @[
+    @{
+      @"agg" : [FIRAggregateField aggregateFieldForAverageOfField:@"intToInt"],
+      @"expected" : [NSNumber numberWithLong:5]
+    },
+    @{
+      @"agg" : [FIRAggregateField aggregateFieldForAverageOfField:@"floatToInt"],
+      @"expected" : [NSNumber numberWithLong:10]
+    },
+    @{
+      @"agg" : [FIRAggregateField aggregateFieldForAverageOfField:@"mixedToInt"],
+      @"expected" : [NSNumber numberWithLong:10]
+    },
+    @{
+      @"agg" : [FIRAggregateField aggregateFieldForAverageOfField:@"floatToFloat"],
+      @"expected" : [NSNumber numberWithDouble:4.5]
+    },
+    @{
+      @"agg" : [FIRAggregateField aggregateFieldForAverageOfField:@"mixedToFloat"],
+      @"expected" : [NSNumber numberWithDouble:9.2]
+    },
+    @{
+      @"agg" : [FIRAggregateField aggregateFieldForAverageOfField:@"intToFloat"],
+      @"expected" : [NSNumber numberWithDouble:9.5]
+    }
+  ];
+
+  for (NSDictionary* testCase in testCases) {
+    FIRAggregateQuerySnapshot* snapshot =
+        [self readSnapshotForAggregate:[testCollection aggregate:@[ testCase[@"agg"] ]]];
+
+    // Average
+    XCTAssertEqual([[snapshot valueForAggregation:testCase[@"agg"]] longValue],
+                   [testCase[@"expected"] longLongValue]);
+    XCTAssertEqualWithAccuracy([[snapshot valueForAggregation:testCase[@"agg"]] doubleValue],
+                               [testCase[@"expected"] doubleValue], 0.00000000000001);
+  }
+}
+
+- (void)testPerformsAverageCausingUnderflow {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{@"rating" : [NSNumber numberWithDouble:__DBL_DENORM_MIN__]},
+    @"b" : @{@"rating" : [NSNumber numberWithDouble:0]}
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForAverageOfField:@"rating"]
+            ]]];
+
+  // Average
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]]
+          doubleValue],
+      [[NSNumber numberWithDouble:0] doubleValue]);
+}
+
+- (void)testPerformsAverageOfMinIEEE754 {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{@"rating" : [NSNumber numberWithDouble:__DBL_DENORM_MIN__]}
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForAverageOfField:@"rating"]
+            ]]];
+
+  // Average
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]]
+          doubleValue],
+      [[NSNumber numberWithDouble:__DBL_DENORM_MIN__] doubleValue]);
+}
+
+- (void)testPerformsAverageOverflowIEEE754DuringAccumulation {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{@"rating" : [NSNumber numberWithDouble:DBL_MAX]},
+    @"b" : @{@"rating" : [NSNumber numberWithDouble:DBL_MAX]}
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForAverageOfField:@"rating"]
+            ]]];
+
+  // Average
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]]
+          doubleValue],
+      [[NSNumber numberWithDouble:INFINITY] doubleValue]);
+}
+
+- (void)testPerformsAverageOverResultSetOfZeroDocuments {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{
+      @"author" : @"authorA",
+      @"title" : @"titleA",
+      @"pages" : @100,
+      @"height" : @24.5,
+      @"weight" : @24.1,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    },
+    @"b" : @{
+      @"author" : @"authorB",
+      @"title" : @"titleB",
+      @"pages" : @50,
+      @"height" : @25.5,
+      @"weight" : @75.5,
+      @"foo" : @1,
+      @"bar" : @2,
+      @"baz" : @3
+    }
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot = [self
+      readSnapshotForAggregate:[[testCollection queryWhereField:@"pages" isGreaterThan:@200]
+                                   aggregate:@[ [FIRAggregateField
+                                                 aggregateFieldForAverageOfField:@"pages"] ]]];
+
+  // Average
+  XCTAssertEqual(
+      [snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"pages"]],
+      [NSNull null]);
+}
+
+- (void)testPerformsAverageOnlyOnNumericFields {
+  // TODO(sum/avg) remove the check below when sum and avg are supported in production
+  XCTSkipIf(![FSTIntegrationTestCase isRunningAgainstEmulator], @"only tested against emulator");
+
+  FIRCollectionReference* testCollection = [self collectionRefWithDocuments:@{
+    @"a" : @{@"rating" : [NSNumber numberWithLong:5]},
+    @"b" : @{@"rating" : [NSNumber numberWithLong:4]},
+    @"c" : @{@"rating" : @"3"},
+    @"d" : @{@"rating" : [NSNumber numberWithLong:6]}
+  }];
+
+  FIRAggregateQuerySnapshot* snapshot =
+      [self readSnapshotForAggregate:[testCollection aggregate:@[
+              [FIRAggregateField aggregateFieldForCount],
+              [FIRAggregateField aggregateFieldForAverageOfField:@"rating"]
+            ]]];
+
+  // Count
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForCount]] longValue], 4L);
+
+  // Average
+  XCTAssertEqual(
+      [[snapshot valueForAggregation:[FIRAggregateField aggregateFieldForAverageOfField:@"rating"]]
+          doubleValue],
+      [[NSNumber numberWithDouble:5] doubleValue]);
+}
+
+@end

+ 1 - 1
Firestore/Protos/README.md

@@ -2,7 +2,7 @@
 
 First, make sure you have necessary prereqs for building:
 ```
-brew install automake libtool protobuf golang
+brew install automake libtool protobuf golang cmake
 ```
 
 Take a nap while that completes. Then, build the protos:

+ 619 - 23
Firestore/Protos/cpp/google/firestore/v1/query.pb.cc

@@ -32,8 +32,10 @@
 #include <google/protobuf/port_def.inc>
 extern PROTOBUF_INTERNAL_EXPORT_google_2ffirestore_2fv1_2fdocument_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<2> scc_info_ArrayValue_google_2ffirestore_2fv1_2fdocument_2eproto;
 extern PROTOBUF_INTERNAL_EXPORT_google_2ffirestore_2fv1_2fquery_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_Cursor_google_2ffirestore_2fv1_2fquery_2eproto;
-extern PROTOBUF_INTERNAL_EXPORT_google_2ffirestore_2fv1_2fquery_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_StructuredAggregationQuery_Aggregation_google_2ffirestore_2fv1_2fquery_2eproto;
+extern PROTOBUF_INTERNAL_EXPORT_google_2ffirestore_2fv1_2fquery_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<3> scc_info_StructuredAggregationQuery_Aggregation_google_2ffirestore_2fv1_2fquery_2eproto;
+extern PROTOBUF_INTERNAL_EXPORT_google_2ffirestore_2fv1_2fquery_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_StructuredAggregationQuery_Aggregation_Avg_google_2ffirestore_2fv1_2fquery_2eproto;
 extern PROTOBUF_INTERNAL_EXPORT_google_2ffirestore_2fv1_2fquery_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_StructuredAggregationQuery_Aggregation_Count_google_2ffirestore_2fv1_2fquery_2eproto;
+extern PROTOBUF_INTERNAL_EXPORT_google_2ffirestore_2fv1_2fquery_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_StructuredAggregationQuery_Aggregation_Sum_google_2ffirestore_2fv1_2fquery_2eproto;
 extern PROTOBUF_INTERNAL_EXPORT_google_2ffirestore_2fv1_2fquery_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<6> scc_info_StructuredQuery_google_2ffirestore_2fv1_2fquery_2eproto;
 extern PROTOBUF_INTERNAL_EXPORT_google_2ffirestore_2fv1_2fquery_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<0> scc_info_StructuredQuery_CollectionSelector_google_2ffirestore_2fv1_2fquery_2eproto;
 extern PROTOBUF_INTERNAL_EXPORT_google_2ffirestore_2fv1_2fquery_2eproto ::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<2> scc_info_StructuredQuery_CompositeFilter_google_2ffirestore_2fv1_2fquery_2eproto;
@@ -91,10 +93,20 @@ class StructuredAggregationQuery_Aggregation_CountDefaultTypeInternal {
  public:
   ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<StructuredAggregationQuery_Aggregation_Count> _instance;
 } _StructuredAggregationQuery_Aggregation_Count_default_instance_;
+class StructuredAggregationQuery_Aggregation_SumDefaultTypeInternal {
+ public:
+  ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<StructuredAggregationQuery_Aggregation_Sum> _instance;
+} _StructuredAggregationQuery_Aggregation_Sum_default_instance_;
+class StructuredAggregationQuery_Aggregation_AvgDefaultTypeInternal {
+ public:
+  ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<StructuredAggregationQuery_Aggregation_Avg> _instance;
+} _StructuredAggregationQuery_Aggregation_Avg_default_instance_;
 class StructuredAggregationQuery_AggregationDefaultTypeInternal {
  public:
   ::PROTOBUF_NAMESPACE_ID::internal::ExplicitlyConstructed<StructuredAggregationQuery_Aggregation> _instance;
   const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count* count_;
+  const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* sum_;
+  const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* avg_;
 } _StructuredAggregationQuery_Aggregation_default_instance_;
 class StructuredAggregationQueryDefaultTypeInternal {
  public:
@@ -150,9 +162,26 @@ static void InitDefaultsscc_info_StructuredAggregationQuery_Aggregation_google_2
   ::google::firestore::v1::StructuredAggregationQuery_Aggregation::InitAsDefaultInstance();
 }
 
-::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_StructuredAggregationQuery_Aggregation_google_2ffirestore_2fv1_2fquery_2eproto =
-    {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 1, 0, InitDefaultsscc_info_StructuredAggregationQuery_Aggregation_google_2ffirestore_2fv1_2fquery_2eproto}, {
-      &scc_info_StructuredAggregationQuery_Aggregation_Count_google_2ffirestore_2fv1_2fquery_2eproto.base,}};
+::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<3> scc_info_StructuredAggregationQuery_Aggregation_google_2ffirestore_2fv1_2fquery_2eproto =
+    {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 3, 0, InitDefaultsscc_info_StructuredAggregationQuery_Aggregation_google_2ffirestore_2fv1_2fquery_2eproto}, {
+      &scc_info_StructuredAggregationQuery_Aggregation_Count_google_2ffirestore_2fv1_2fquery_2eproto.base,
+      &scc_info_StructuredAggregationQuery_Aggregation_Sum_google_2ffirestore_2fv1_2fquery_2eproto.base,
+      &scc_info_StructuredAggregationQuery_Aggregation_Avg_google_2ffirestore_2fv1_2fquery_2eproto.base,}};
+
+static void InitDefaultsscc_info_StructuredAggregationQuery_Aggregation_Avg_google_2ffirestore_2fv1_2fquery_2eproto() {
+  GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+  {
+    void* ptr = &::google::firestore::v1::_StructuredAggregationQuery_Aggregation_Avg_default_instance_;
+    new (ptr) ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg();
+    ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr);
+  }
+  ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg::InitAsDefaultInstance();
+}
+
+::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_StructuredAggregationQuery_Aggregation_Avg_google_2ffirestore_2fv1_2fquery_2eproto =
+    {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 1, 0, InitDefaultsscc_info_StructuredAggregationQuery_Aggregation_Avg_google_2ffirestore_2fv1_2fquery_2eproto}, {
+      &scc_info_StructuredQuery_FieldReference_google_2ffirestore_2fv1_2fquery_2eproto.base,}};
 
 static void InitDefaultsscc_info_StructuredAggregationQuery_Aggregation_Count_google_2ffirestore_2fv1_2fquery_2eproto() {
   GOOGLE_PROTOBUF_VERIFY_VERSION;
@@ -169,6 +198,21 @@ static void InitDefaultsscc_info_StructuredAggregationQuery_Aggregation_Count_go
     {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 1, 0, InitDefaultsscc_info_StructuredAggregationQuery_Aggregation_Count_google_2ffirestore_2fv1_2fquery_2eproto}, {
       &scc_info_Int64Value_google_2fprotobuf_2fwrappers_2eproto.base,}};
 
+static void InitDefaultsscc_info_StructuredAggregationQuery_Aggregation_Sum_google_2ffirestore_2fv1_2fquery_2eproto() {
+  GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+  {
+    void* ptr = &::google::firestore::v1::_StructuredAggregationQuery_Aggregation_Sum_default_instance_;
+    new (ptr) ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum();
+    ::PROTOBUF_NAMESPACE_ID::internal::OnShutdownDestroyMessage(ptr);
+  }
+  ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum::InitAsDefaultInstance();
+}
+
+::PROTOBUF_NAMESPACE_ID::internal::SCCInfo<1> scc_info_StructuredAggregationQuery_Aggregation_Sum_google_2ffirestore_2fv1_2fquery_2eproto =
+    {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 1, 0, InitDefaultsscc_info_StructuredAggregationQuery_Aggregation_Sum_google_2ffirestore_2fv1_2fquery_2eproto}, {
+      &scc_info_StructuredQuery_FieldReference_google_2ffirestore_2fv1_2fquery_2eproto.base,}};
+
 static void InitDefaultsscc_info_StructuredQuery_google_2ffirestore_2fv1_2fquery_2eproto() {
   GOOGLE_PROTOBUF_VERIFY_VERSION;
 
@@ -300,7 +344,7 @@ static void InitDefaultsscc_info_StructuredQuery_UnaryFilter_google_2ffirestore_
     {{ATOMIC_VAR_INIT(::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase::kUninitialized), 1, 0, InitDefaultsscc_info_StructuredQuery_UnaryFilter_google_2ffirestore_2fv1_2fquery_2eproto}, {
       &scc_info_StructuredQuery_FieldReference_google_2ffirestore_2fv1_2fquery_2eproto.base,}};
 
-static ::PROTOBUF_NAMESPACE_ID::Metadata file_level_metadata_google_2ffirestore_2fv1_2fquery_2eproto[13];
+static ::PROTOBUF_NAMESPACE_ID::Metadata file_level_metadata_google_2ffirestore_2fv1_2fquery_2eproto[15];
 static const ::PROTOBUF_NAMESPACE_ID::EnumDescriptor* file_level_enum_descriptors_google_2ffirestore_2fv1_2fquery_2eproto[4];
 static constexpr ::PROTOBUF_NAMESPACE_ID::ServiceDescriptor const** file_level_service_descriptors_google_2ffirestore_2fv1_2fquery_2eproto = nullptr;
 
@@ -383,11 +427,25 @@ const ::PROTOBUF_NAMESPACE_ID::uint32 TableStruct_google_2ffirestore_2fv1_2fquer
   ~0u,  // no _weak_field_map_
   PROTOBUF_FIELD_OFFSET(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count, up_to_),
   ~0u,  // no _has_bits_
+  PROTOBUF_FIELD_OFFSET(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum, _internal_metadata_),
+  ~0u,  // no _extensions_
+  ~0u,  // no _oneof_case_
+  ~0u,  // no _weak_field_map_
+  PROTOBUF_FIELD_OFFSET(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum, field_),
+  ~0u,  // no _has_bits_
+  PROTOBUF_FIELD_OFFSET(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg, _internal_metadata_),
+  ~0u,  // no _extensions_
+  ~0u,  // no _oneof_case_
+  ~0u,  // no _weak_field_map_
+  PROTOBUF_FIELD_OFFSET(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg, field_),
+  ~0u,  // no _has_bits_
   PROTOBUF_FIELD_OFFSET(::google::firestore::v1::StructuredAggregationQuery_Aggregation, _internal_metadata_),
   ~0u,  // no _extensions_
   PROTOBUF_FIELD_OFFSET(::google::firestore::v1::StructuredAggregationQuery_Aggregation, _oneof_case_[0]),
   ~0u,  // no _weak_field_map_
   offsetof(::google::firestore::v1::StructuredAggregationQuery_AggregationDefaultTypeInternal, count_),
+  offsetof(::google::firestore::v1::StructuredAggregationQuery_AggregationDefaultTypeInternal, sum_),
+  offsetof(::google::firestore::v1::StructuredAggregationQuery_AggregationDefaultTypeInternal, avg_),
   PROTOBUF_FIELD_OFFSET(::google::firestore::v1::StructuredAggregationQuery_Aggregation, alias_),
   PROTOBUF_FIELD_OFFSET(::google::firestore::v1::StructuredAggregationQuery_Aggregation, operator_),
   ~0u,  // no _has_bits_
@@ -417,9 +475,11 @@ static const ::PROTOBUF_NAMESPACE_ID::internal::MigrationSchema schemas[] PROTOB
   { 52, -1, sizeof(::google::firestore::v1::StructuredQuery_Projection)},
   { 58, -1, sizeof(::google::firestore::v1::StructuredQuery)},
   { 71, -1, sizeof(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count)},
-  { 77, -1, sizeof(::google::firestore::v1::StructuredAggregationQuery_Aggregation)},
-  { 85, -1, sizeof(::google::firestore::v1::StructuredAggregationQuery)},
-  { 93, -1, sizeof(::google::firestore::v1::Cursor)},
+  { 77, -1, sizeof(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum)},
+  { 83, -1, sizeof(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg)},
+  { 89, -1, sizeof(::google::firestore::v1::StructuredAggregationQuery_Aggregation)},
+  { 99, -1, sizeof(::google::firestore::v1::StructuredAggregationQuery)},
+  { 107, -1, sizeof(::google::firestore::v1::Cursor)},
 };
 
 static ::PROTOBUF_NAMESPACE_ID::Message const * const file_default_instances[] = {
@@ -433,6 +493,8 @@ static ::PROTOBUF_NAMESPACE_ID::Message const * const file_default_instances[] =
   reinterpret_cast<const ::PROTOBUF_NAMESPACE_ID::Message*>(&::google::firestore::v1::_StructuredQuery_Projection_default_instance_),
   reinterpret_cast<const ::PROTOBUF_NAMESPACE_ID::Message*>(&::google::firestore::v1::_StructuredQuery_default_instance_),
   reinterpret_cast<const ::PROTOBUF_NAMESPACE_ID::Message*>(&::google::firestore::v1::_StructuredAggregationQuery_Aggregation_Count_default_instance_),
+  reinterpret_cast<const ::PROTOBUF_NAMESPACE_ID::Message*>(&::google::firestore::v1::_StructuredAggregationQuery_Aggregation_Sum_default_instance_),
+  reinterpret_cast<const ::PROTOBUF_NAMESPACE_ID::Message*>(&::google::firestore::v1::_StructuredAggregationQuery_Aggregation_Avg_default_instance_),
   reinterpret_cast<const ::PROTOBUF_NAMESPACE_ID::Message*>(&::google::firestore::v1::_StructuredAggregationQuery_Aggregation_default_instance_),
   reinterpret_cast<const ::PROTOBUF_NAMESPACE_ID::Message*>(&::google::firestore::v1::_StructuredAggregationQuery_default_instance_),
   reinterpret_cast<const ::PROTOBUF_NAMESPACE_ID::Message*>(&::google::firestore::v1::_Cursor_default_instance_),
@@ -491,33 +553,43 @@ const char descriptor_table_protodef_google_2ffirestore_2fv1_2fquery_2eproto[] P
   "jection\022C\n\006fields\030\002 \003(\01323.google.firesto"
   "re.v1.StructuredQuery.FieldReference\"E\n\t"
   "Direction\022\031\n\025DIRECTION_UNSPECIFIED\020\000\022\r\n\t"
-  "ASCENDING\020\001\022\016\n\nDESCENDING\020\002\"\363\002\n\032Structur"
+  "ASCENDING\020\001\022\016\n\nDESCENDING\020\002\"\251\005\n\032Structur"
   "edAggregationQuery\022@\n\020structured_query\030\001"
   " \001(\0132$.google.firestore.v1.StructuredQue"
   "ryH\000\022Q\n\014aggregations\030\003 \003(\0132;.google.fire"
   "store.v1.StructuredAggregationQuery.Aggr"
-  "egation\032\261\001\n\013Aggregation\022R\n\005count\030\001 \001(\0132A"
+  "egation\032\347\003\n\013Aggregation\022R\n\005count\030\001 \001(\0132A"
   ".google.firestore.v1.StructuredAggregati"
-  "onQuery.Aggregation.CountH\000\022\r\n\005alias\030\007 \001"
+  "onQuery.Aggregation.CountH\000\022N\n\003sum\030\002 \001(\013"
+  "2\?.google.firestore.v1.StructuredAggrega"
+  "tionQuery.Aggregation.SumH\000\022N\n\003avg\030\003 \001(\013"
+  "2\?.google.firestore.v1.StructuredAggrega"
+  "tionQuery.Aggregation.AvgH\000\022\r\n\005alias\030\007 \001"
   "(\t\0323\n\005Count\022*\n\005up_to\030\001 \001(\0132\033.google.prot"
-  "obuf.Int64ValueB\n\n\010operatorB\014\n\nquery_typ"
-  "e\"D\n\006Cursor\022*\n\006values\030\001 \003(\0132\032.google.fir"
-  "estore.v1.Value\022\016\n\006before\030\002 \001(\010B\256\001\n\027com."
-  "google.firestore.v1B\nQueryProtoP\001Z<googl"
-  "e.golang.org/genproto/googleapis/firesto"
-  "re/v1;firestore\242\002\004GCFS\252\002\036Google.Cloud.Fi"
-  "restore.V1Beta1\312\002\036Google\\Cloud\\Firestore"
-  "\\V1beta1b\006proto3"
+  "obuf.Int64Value\032I\n\003Sum\022B\n\005field\030\001 \001(\01323."
+  "google.firestore.v1.StructuredQuery.Fiel"
+  "dReference\032I\n\003Avg\022B\n\005field\030\001 \001(\01323.googl"
+  "e.firestore.v1.StructuredQuery.FieldRefe"
+  "renceB\n\n\010operatorB\014\n\nquery_type\"D\n\006Curso"
+  "r\022*\n\006values\030\001 \003(\0132\032.google.firestore.v1."
+  "Value\022\016\n\006before\030\002 \001(\010B\256\001\n\027com.google.fir"
+  "estore.v1B\nQueryProtoP\001Z<google.golang.o"
+  "rg/genproto/googleapis/firestore/v1;fire"
+  "store\242\002\004GCFS\252\002\036Google.Cloud.Firestore.V1"
+  "Beta1\312\002\036Google\\Cloud\\Firestore\\V1beta1b\006"
+  "proto3"
   ;
 static const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable*const descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_deps[2] = {
   &::descriptor_table_google_2ffirestore_2fv1_2fdocument_2eproto,
   &::descriptor_table_google_2fprotobuf_2fwrappers_2eproto,
 };
-static ::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase*const descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_sccs[12] = {
+static ::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase*const descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_sccs[14] = {
   &scc_info_Cursor_google_2ffirestore_2fv1_2fquery_2eproto.base,
   &scc_info_StructuredAggregationQuery_google_2ffirestore_2fv1_2fquery_2eproto.base,
   &scc_info_StructuredAggregationQuery_Aggregation_google_2ffirestore_2fv1_2fquery_2eproto.base,
+  &scc_info_StructuredAggregationQuery_Aggregation_Avg_google_2ffirestore_2fv1_2fquery_2eproto.base,
   &scc_info_StructuredAggregationQuery_Aggregation_Count_google_2ffirestore_2fv1_2fquery_2eproto.base,
+  &scc_info_StructuredAggregationQuery_Aggregation_Sum_google_2ffirestore_2fv1_2fquery_2eproto.base,
   &scc_info_StructuredQuery_google_2ffirestore_2fv1_2fquery_2eproto.base,
   &scc_info_StructuredQuery_CollectionSelector_google_2ffirestore_2fv1_2fquery_2eproto.base,
   &scc_info_StructuredQuery_CompositeFilter_google_2ffirestore_2fv1_2fquery_2eproto.base,
@@ -530,10 +602,10 @@ static ::PROTOBUF_NAMESPACE_ID::internal::SCCInfoBase*const descriptor_table_goo
 static ::PROTOBUF_NAMESPACE_ID::internal::once_flag descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_once;
 static bool descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_initialized = false;
 const ::PROTOBUF_NAMESPACE_ID::internal::DescriptorTable descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto = {
-  &descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_initialized, descriptor_table_protodef_google_2ffirestore_2fv1_2fquery_2eproto, "google/firestore/v1/query.proto", 2736,
-  &descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_once, descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_sccs, descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_deps, 12, 2,
+  &descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_initialized, descriptor_table_protodef_google_2ffirestore_2fv1_2fquery_2eproto, "google/firestore/v1/query.proto", 3046,
+  &descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_once, descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_sccs, descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto_deps, 14, 2,
   schemas, file_default_instances, TableStruct_google_2ffirestore_2fv1_2fquery_2eproto::offsets,
-  file_level_metadata_google_2ffirestore_2fv1_2fquery_2eproto, 13, file_level_enum_descriptors_google_2ffirestore_2fv1_2fquery_2eproto, file_level_service_descriptors_google_2ffirestore_2fv1_2fquery_2eproto,
+  file_level_metadata_google_2ffirestore_2fv1_2fquery_2eproto, 15, file_level_enum_descriptors_google_2ffirestore_2fv1_2fquery_2eproto, file_level_service_descriptors_google_2ffirestore_2fv1_2fquery_2eproto,
 };
 
 // Force running AddDescriptors() at dynamic initialization time.
@@ -3320,21 +3392,443 @@ void StructuredAggregationQuery_Aggregation_Count::InternalSwap(StructuredAggreg
 }
 
 
+// ===================================================================
+
+void StructuredAggregationQuery_Aggregation_Sum::InitAsDefaultInstance() {
+  ::google::firestore::v1::_StructuredAggregationQuery_Aggregation_Sum_default_instance_._instance.get_mutable()->field_ = const_cast< ::google::firestore::v1::StructuredQuery_FieldReference*>(
+      ::google::firestore::v1::StructuredQuery_FieldReference::internal_default_instance());
+}
+class StructuredAggregationQuery_Aggregation_Sum::_Internal {
+ public:
+  static const ::google::firestore::v1::StructuredQuery_FieldReference& field(const StructuredAggregationQuery_Aggregation_Sum* msg);
+};
+
+const ::google::firestore::v1::StructuredQuery_FieldReference&
+StructuredAggregationQuery_Aggregation_Sum::_Internal::field(const StructuredAggregationQuery_Aggregation_Sum* msg) {
+  return *msg->field_;
+}
+StructuredAggregationQuery_Aggregation_Sum::StructuredAggregationQuery_Aggregation_Sum()
+  : ::PROTOBUF_NAMESPACE_ID::Message(), _internal_metadata_(nullptr) {
+  SharedCtor();
+  // @@protoc_insertion_point(constructor:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+}
+StructuredAggregationQuery_Aggregation_Sum::StructuredAggregationQuery_Aggregation_Sum(const StructuredAggregationQuery_Aggregation_Sum& from)
+  : ::PROTOBUF_NAMESPACE_ID::Message(),
+      _internal_metadata_(nullptr) {
+  _internal_metadata_.MergeFrom(from._internal_metadata_);
+  if (from._internal_has_field()) {
+    field_ = new ::google::firestore::v1::StructuredQuery_FieldReference(*from.field_);
+  } else {
+    field_ = nullptr;
+  }
+  // @@protoc_insertion_point(copy_constructor:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+}
+
+void StructuredAggregationQuery_Aggregation_Sum::SharedCtor() {
+  ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&scc_info_StructuredAggregationQuery_Aggregation_Sum_google_2ffirestore_2fv1_2fquery_2eproto.base);
+  field_ = nullptr;
+}
+
+StructuredAggregationQuery_Aggregation_Sum::~StructuredAggregationQuery_Aggregation_Sum() {
+  // @@protoc_insertion_point(destructor:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+  SharedDtor();
+}
+
+void StructuredAggregationQuery_Aggregation_Sum::SharedDtor() {
+  if (this != internal_default_instance()) delete field_;
+}
+
+void StructuredAggregationQuery_Aggregation_Sum::SetCachedSize(int size) const {
+  _cached_size_.Set(size);
+}
+const StructuredAggregationQuery_Aggregation_Sum& StructuredAggregationQuery_Aggregation_Sum::default_instance() {
+  ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_StructuredAggregationQuery_Aggregation_Sum_google_2ffirestore_2fv1_2fquery_2eproto.base);
+  return *internal_default_instance();
+}
+
+
+void StructuredAggregationQuery_Aggregation_Sum::Clear() {
+// @@protoc_insertion_point(message_clear_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+  ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0;
+  // Prevent compiler warnings about cached_has_bits being unused
+  (void) cached_has_bits;
+
+  if (GetArenaNoVirtual() == nullptr && field_ != nullptr) {
+    delete field_;
+  }
+  field_ = nullptr;
+  _internal_metadata_.Clear();
+}
+
+const char* StructuredAggregationQuery_Aggregation_Sum::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) {
+#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure
+  while (!ctx->Done(&ptr)) {
+    ::PROTOBUF_NAMESPACE_ID::uint32 tag;
+    ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag);
+    CHK_(ptr);
+    switch (tag >> 3) {
+      // .google.firestore.v1.StructuredQuery.FieldReference field = 1;
+      case 1:
+        if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 10)) {
+          ptr = ctx->ParseMessage(_internal_mutable_field(), ptr);
+          CHK_(ptr);
+        } else goto handle_unusual;
+        continue;
+      default: {
+      handle_unusual:
+        if ((tag & 7) == 4 || tag == 0) {
+          ctx->SetLastTag(tag);
+          goto success;
+        }
+        ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx);
+        CHK_(ptr != nullptr);
+        continue;
+      }
+    }  // switch
+  }  // while
+success:
+  return ptr;
+failure:
+  ptr = nullptr;
+  goto success;
+#undef CHK_
+}
+
+::PROTOBUF_NAMESPACE_ID::uint8* StructuredAggregationQuery_Aggregation_Sum::_InternalSerialize(
+    ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const {
+  // @@protoc_insertion_point(serialize_to_array_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+  ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0;
+  (void) cached_has_bits;
+
+  // .google.firestore.v1.StructuredQuery.FieldReference field = 1;
+  if (this->has_field()) {
+    target = stream->EnsureSpace(target);
+    target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::
+      InternalWriteMessage(
+        1, _Internal::field(this), target, stream);
+  }
+
+  if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+    target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormat::InternalSerializeUnknownFieldsToArray(
+        _internal_metadata_.unknown_fields(), target, stream);
+  }
+  // @@protoc_insertion_point(serialize_to_array_end:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+  return target;
+}
+
+size_t StructuredAggregationQuery_Aggregation_Sum::ByteSizeLong() const {
+// @@protoc_insertion_point(message_byte_size_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+  size_t total_size = 0;
+
+  ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0;
+  // Prevent compiler warnings about cached_has_bits being unused
+  (void) cached_has_bits;
+
+  // .google.firestore.v1.StructuredQuery.FieldReference field = 1;
+  if (this->has_field()) {
+    total_size += 1 +
+      ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(
+        *field_);
+  }
+
+  if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+    return ::PROTOBUF_NAMESPACE_ID::internal::ComputeUnknownFieldsSize(
+        _internal_metadata_, total_size, &_cached_size_);
+  }
+  int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size);
+  SetCachedSize(cached_size);
+  return total_size;
+}
+
+void StructuredAggregationQuery_Aggregation_Sum::MergeFrom(const ::PROTOBUF_NAMESPACE_ID::Message& from) {
+// @@protoc_insertion_point(generalized_merge_from_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+  GOOGLE_DCHECK_NE(&from, this);
+  const StructuredAggregationQuery_Aggregation_Sum* source =
+      ::PROTOBUF_NAMESPACE_ID::DynamicCastToGenerated<StructuredAggregationQuery_Aggregation_Sum>(
+          &from);
+  if (source == nullptr) {
+  // @@protoc_insertion_point(generalized_merge_from_cast_fail:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+    ::PROTOBUF_NAMESPACE_ID::internal::ReflectionOps::Merge(from, this);
+  } else {
+  // @@protoc_insertion_point(generalized_merge_from_cast_success:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+    MergeFrom(*source);
+  }
+}
+
+void StructuredAggregationQuery_Aggregation_Sum::MergeFrom(const StructuredAggregationQuery_Aggregation_Sum& from) {
+// @@protoc_insertion_point(class_specific_merge_from_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+  GOOGLE_DCHECK_NE(&from, this);
+  _internal_metadata_.MergeFrom(from._internal_metadata_);
+  ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0;
+  (void) cached_has_bits;
+
+  if (from.has_field()) {
+    _internal_mutable_field()->::google::firestore::v1::StructuredQuery_FieldReference::MergeFrom(from._internal_field());
+  }
+}
+
+void StructuredAggregationQuery_Aggregation_Sum::CopyFrom(const ::PROTOBUF_NAMESPACE_ID::Message& from) {
+// @@protoc_insertion_point(generalized_copy_from_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+void StructuredAggregationQuery_Aggregation_Sum::CopyFrom(const StructuredAggregationQuery_Aggregation_Sum& from) {
+// @@protoc_insertion_point(class_specific_copy_from_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+bool StructuredAggregationQuery_Aggregation_Sum::IsInitialized() const {
+  return true;
+}
+
+void StructuredAggregationQuery_Aggregation_Sum::InternalSwap(StructuredAggregationQuery_Aggregation_Sum* other) {
+  using std::swap;
+  _internal_metadata_.Swap(&other->_internal_metadata_);
+  swap(field_, other->field_);
+}
+
+::PROTOBUF_NAMESPACE_ID::Metadata StructuredAggregationQuery_Aggregation_Sum::GetMetadata() const {
+  return GetMetadataStatic();
+}
+
+
+// ===================================================================
+
+void StructuredAggregationQuery_Aggregation_Avg::InitAsDefaultInstance() {
+  ::google::firestore::v1::_StructuredAggregationQuery_Aggregation_Avg_default_instance_._instance.get_mutable()->field_ = const_cast< ::google::firestore::v1::StructuredQuery_FieldReference*>(
+      ::google::firestore::v1::StructuredQuery_FieldReference::internal_default_instance());
+}
+class StructuredAggregationQuery_Aggregation_Avg::_Internal {
+ public:
+  static const ::google::firestore::v1::StructuredQuery_FieldReference& field(const StructuredAggregationQuery_Aggregation_Avg* msg);
+};
+
+const ::google::firestore::v1::StructuredQuery_FieldReference&
+StructuredAggregationQuery_Aggregation_Avg::_Internal::field(const StructuredAggregationQuery_Aggregation_Avg* msg) {
+  return *msg->field_;
+}
+StructuredAggregationQuery_Aggregation_Avg::StructuredAggregationQuery_Aggregation_Avg()
+  : ::PROTOBUF_NAMESPACE_ID::Message(), _internal_metadata_(nullptr) {
+  SharedCtor();
+  // @@protoc_insertion_point(constructor:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+}
+StructuredAggregationQuery_Aggregation_Avg::StructuredAggregationQuery_Aggregation_Avg(const StructuredAggregationQuery_Aggregation_Avg& from)
+  : ::PROTOBUF_NAMESPACE_ID::Message(),
+      _internal_metadata_(nullptr) {
+  _internal_metadata_.MergeFrom(from._internal_metadata_);
+  if (from._internal_has_field()) {
+    field_ = new ::google::firestore::v1::StructuredQuery_FieldReference(*from.field_);
+  } else {
+    field_ = nullptr;
+  }
+  // @@protoc_insertion_point(copy_constructor:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+}
+
+void StructuredAggregationQuery_Aggregation_Avg::SharedCtor() {
+  ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&scc_info_StructuredAggregationQuery_Aggregation_Avg_google_2ffirestore_2fv1_2fquery_2eproto.base);
+  field_ = nullptr;
+}
+
+StructuredAggregationQuery_Aggregation_Avg::~StructuredAggregationQuery_Aggregation_Avg() {
+  // @@protoc_insertion_point(destructor:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+  SharedDtor();
+}
+
+void StructuredAggregationQuery_Aggregation_Avg::SharedDtor() {
+  if (this != internal_default_instance()) delete field_;
+}
+
+void StructuredAggregationQuery_Aggregation_Avg::SetCachedSize(int size) const {
+  _cached_size_.Set(size);
+}
+const StructuredAggregationQuery_Aggregation_Avg& StructuredAggregationQuery_Aggregation_Avg::default_instance() {
+  ::PROTOBUF_NAMESPACE_ID::internal::InitSCC(&::scc_info_StructuredAggregationQuery_Aggregation_Avg_google_2ffirestore_2fv1_2fquery_2eproto.base);
+  return *internal_default_instance();
+}
+
+
+void StructuredAggregationQuery_Aggregation_Avg::Clear() {
+// @@protoc_insertion_point(message_clear_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+  ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0;
+  // Prevent compiler warnings about cached_has_bits being unused
+  (void) cached_has_bits;
+
+  if (GetArenaNoVirtual() == nullptr && field_ != nullptr) {
+    delete field_;
+  }
+  field_ = nullptr;
+  _internal_metadata_.Clear();
+}
+
+const char* StructuredAggregationQuery_Aggregation_Avg::_InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) {
+#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure
+  while (!ctx->Done(&ptr)) {
+    ::PROTOBUF_NAMESPACE_ID::uint32 tag;
+    ptr = ::PROTOBUF_NAMESPACE_ID::internal::ReadTag(ptr, &tag);
+    CHK_(ptr);
+    switch (tag >> 3) {
+      // .google.firestore.v1.StructuredQuery.FieldReference field = 1;
+      case 1:
+        if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 10)) {
+          ptr = ctx->ParseMessage(_internal_mutable_field(), ptr);
+          CHK_(ptr);
+        } else goto handle_unusual;
+        continue;
+      default: {
+      handle_unusual:
+        if ((tag & 7) == 4 || tag == 0) {
+          ctx->SetLastTag(tag);
+          goto success;
+        }
+        ptr = UnknownFieldParse(tag, &_internal_metadata_, ptr, ctx);
+        CHK_(ptr != nullptr);
+        continue;
+      }
+    }  // switch
+  }  // while
+success:
+  return ptr;
+failure:
+  ptr = nullptr;
+  goto success;
+#undef CHK_
+}
+
+::PROTOBUF_NAMESPACE_ID::uint8* StructuredAggregationQuery_Aggregation_Avg::_InternalSerialize(
+    ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const {
+  // @@protoc_insertion_point(serialize_to_array_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+  ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0;
+  (void) cached_has_bits;
+
+  // .google.firestore.v1.StructuredQuery.FieldReference field = 1;
+  if (this->has_field()) {
+    target = stream->EnsureSpace(target);
+    target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::
+      InternalWriteMessage(
+        1, _Internal::field(this), target, stream);
+  }
+
+  if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+    target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormat::InternalSerializeUnknownFieldsToArray(
+        _internal_metadata_.unknown_fields(), target, stream);
+  }
+  // @@protoc_insertion_point(serialize_to_array_end:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+  return target;
+}
+
+size_t StructuredAggregationQuery_Aggregation_Avg::ByteSizeLong() const {
+// @@protoc_insertion_point(message_byte_size_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+  size_t total_size = 0;
+
+  ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0;
+  // Prevent compiler warnings about cached_has_bits being unused
+  (void) cached_has_bits;
+
+  // .google.firestore.v1.StructuredQuery.FieldReference field = 1;
+  if (this->has_field()) {
+    total_size += 1 +
+      ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(
+        *field_);
+  }
+
+  if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+    return ::PROTOBUF_NAMESPACE_ID::internal::ComputeUnknownFieldsSize(
+        _internal_metadata_, total_size, &_cached_size_);
+  }
+  int cached_size = ::PROTOBUF_NAMESPACE_ID::internal::ToCachedSize(total_size);
+  SetCachedSize(cached_size);
+  return total_size;
+}
+
+void StructuredAggregationQuery_Aggregation_Avg::MergeFrom(const ::PROTOBUF_NAMESPACE_ID::Message& from) {
+// @@protoc_insertion_point(generalized_merge_from_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+  GOOGLE_DCHECK_NE(&from, this);
+  const StructuredAggregationQuery_Aggregation_Avg* source =
+      ::PROTOBUF_NAMESPACE_ID::DynamicCastToGenerated<StructuredAggregationQuery_Aggregation_Avg>(
+          &from);
+  if (source == nullptr) {
+  // @@protoc_insertion_point(generalized_merge_from_cast_fail:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+    ::PROTOBUF_NAMESPACE_ID::internal::ReflectionOps::Merge(from, this);
+  } else {
+  // @@protoc_insertion_point(generalized_merge_from_cast_success:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+    MergeFrom(*source);
+  }
+}
+
+void StructuredAggregationQuery_Aggregation_Avg::MergeFrom(const StructuredAggregationQuery_Aggregation_Avg& from) {
+// @@protoc_insertion_point(class_specific_merge_from_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+  GOOGLE_DCHECK_NE(&from, this);
+  _internal_metadata_.MergeFrom(from._internal_metadata_);
+  ::PROTOBUF_NAMESPACE_ID::uint32 cached_has_bits = 0;
+  (void) cached_has_bits;
+
+  if (from.has_field()) {
+    _internal_mutable_field()->::google::firestore::v1::StructuredQuery_FieldReference::MergeFrom(from._internal_field());
+  }
+}
+
+void StructuredAggregationQuery_Aggregation_Avg::CopyFrom(const ::PROTOBUF_NAMESPACE_ID::Message& from) {
+// @@protoc_insertion_point(generalized_copy_from_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+void StructuredAggregationQuery_Aggregation_Avg::CopyFrom(const StructuredAggregationQuery_Aggregation_Avg& from) {
+// @@protoc_insertion_point(class_specific_copy_from_start:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+  if (&from == this) return;
+  Clear();
+  MergeFrom(from);
+}
+
+bool StructuredAggregationQuery_Aggregation_Avg::IsInitialized() const {
+  return true;
+}
+
+void StructuredAggregationQuery_Aggregation_Avg::InternalSwap(StructuredAggregationQuery_Aggregation_Avg* other) {
+  using std::swap;
+  _internal_metadata_.Swap(&other->_internal_metadata_);
+  swap(field_, other->field_);
+}
+
+::PROTOBUF_NAMESPACE_ID::Metadata StructuredAggregationQuery_Aggregation_Avg::GetMetadata() const {
+  return GetMetadataStatic();
+}
+
+
 // ===================================================================
 
 void StructuredAggregationQuery_Aggregation::InitAsDefaultInstance() {
   ::google::firestore::v1::_StructuredAggregationQuery_Aggregation_default_instance_.count_ = const_cast< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count*>(
       ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count::internal_default_instance());
+  ::google::firestore::v1::_StructuredAggregationQuery_Aggregation_default_instance_.sum_ = const_cast< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum*>(
+      ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum::internal_default_instance());
+  ::google::firestore::v1::_StructuredAggregationQuery_Aggregation_default_instance_.avg_ = const_cast< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg*>(
+      ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg::internal_default_instance());
 }
 class StructuredAggregationQuery_Aggregation::_Internal {
  public:
   static const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count& count(const StructuredAggregationQuery_Aggregation* msg);
+  static const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum& sum(const StructuredAggregationQuery_Aggregation* msg);
+  static const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg& avg(const StructuredAggregationQuery_Aggregation* msg);
 };
 
 const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count&
 StructuredAggregationQuery_Aggregation::_Internal::count(const StructuredAggregationQuery_Aggregation* msg) {
   return *msg->operator_.count_;
 }
+const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum&
+StructuredAggregationQuery_Aggregation::_Internal::sum(const StructuredAggregationQuery_Aggregation* msg) {
+  return *msg->operator_.sum_;
+}
+const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg&
+StructuredAggregationQuery_Aggregation::_Internal::avg(const StructuredAggregationQuery_Aggregation* msg) {
+  return *msg->operator_.avg_;
+}
 void StructuredAggregationQuery_Aggregation::set_allocated_count(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count* count) {
   ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual();
   clear_operator();
@@ -3349,6 +3843,34 @@ void StructuredAggregationQuery_Aggregation::set_allocated_count(::google::fires
   }
   // @@protoc_insertion_point(field_set_allocated:google.firestore.v1.StructuredAggregationQuery.Aggregation.count)
 }
+void StructuredAggregationQuery_Aggregation::set_allocated_sum(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* sum) {
+  ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual();
+  clear_operator();
+  if (sum) {
+    ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr;
+    if (message_arena != submessage_arena) {
+      sum = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage(
+          message_arena, sum, submessage_arena);
+    }
+    set_has_sum();
+    operator_.sum_ = sum;
+  }
+  // @@protoc_insertion_point(field_set_allocated:google.firestore.v1.StructuredAggregationQuery.Aggregation.sum)
+}
+void StructuredAggregationQuery_Aggregation::set_allocated_avg(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* avg) {
+  ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual();
+  clear_operator();
+  if (avg) {
+    ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr;
+    if (message_arena != submessage_arena) {
+      avg = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage(
+          message_arena, avg, submessage_arena);
+    }
+    set_has_avg();
+    operator_.avg_ = avg;
+  }
+  // @@protoc_insertion_point(field_set_allocated:google.firestore.v1.StructuredAggregationQuery.Aggregation.avg)
+}
 StructuredAggregationQuery_Aggregation::StructuredAggregationQuery_Aggregation()
   : ::PROTOBUF_NAMESPACE_ID::Message(), _internal_metadata_(nullptr) {
   SharedCtor();
@@ -3368,6 +3890,14 @@ StructuredAggregationQuery_Aggregation::StructuredAggregationQuery_Aggregation(c
       _internal_mutable_count()->::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count::MergeFrom(from._internal_count());
       break;
     }
+    case kSum: {
+      _internal_mutable_sum()->::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum::MergeFrom(from._internal_sum());
+      break;
+    }
+    case kAvg: {
+      _internal_mutable_avg()->::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg::MergeFrom(from._internal_avg());
+      break;
+    }
     case OPERATOR_NOT_SET: {
       break;
     }
@@ -3409,6 +3939,14 @@ void StructuredAggregationQuery_Aggregation::clear_operator() {
       delete operator_.count_;
       break;
     }
+    case kSum: {
+      delete operator_.sum_;
+      break;
+    }
+    case kAvg: {
+      delete operator_.avg_;
+      break;
+    }
     case OPERATOR_NOT_SET: {
       break;
     }
@@ -3442,6 +3980,20 @@ const char* StructuredAggregationQuery_Aggregation::_InternalParse(const char* p
           CHK_(ptr);
         } else goto handle_unusual;
         continue;
+      // .google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum sum = 2;
+      case 2:
+        if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 18)) {
+          ptr = ctx->ParseMessage(_internal_mutable_sum(), ptr);
+          CHK_(ptr);
+        } else goto handle_unusual;
+        continue;
+      // .google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg avg = 3;
+      case 3:
+        if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 26)) {
+          ptr = ctx->ParseMessage(_internal_mutable_avg(), ptr);
+          CHK_(ptr);
+        } else goto handle_unusual;
+        continue;
       // string alias = 7;
       case 7:
         if (PROTOBUF_PREDICT_TRUE(static_cast<::PROTOBUF_NAMESPACE_ID::uint8>(tag) == 58)) {
@@ -3485,6 +4037,22 @@ failure:
         1, _Internal::count(this), target, stream);
   }
 
+  // .google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum sum = 2;
+  if (_internal_has_sum()) {
+    target = stream->EnsureSpace(target);
+    target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::
+      InternalWriteMessage(
+        2, _Internal::sum(this), target, stream);
+  }
+
+  // .google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg avg = 3;
+  if (_internal_has_avg()) {
+    target = stream->EnsureSpace(target);
+    target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::
+      InternalWriteMessage(
+        3, _Internal::avg(this), target, stream);
+  }
+
   // string alias = 7;
   if (this->alias().size() > 0) {
     ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String(
@@ -3526,6 +4094,20 @@ size_t StructuredAggregationQuery_Aggregation::ByteSizeLong() const {
           *operator_.count_);
       break;
     }
+    // .google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum sum = 2;
+    case kSum: {
+      total_size += 1 +
+        ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(
+          *operator_.sum_);
+      break;
+    }
+    // .google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg avg = 3;
+    case kAvg: {
+      total_size += 1 +
+        ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(
+          *operator_.avg_);
+      break;
+    }
     case OPERATOR_NOT_SET: {
       break;
     }
@@ -3570,6 +4152,14 @@ void StructuredAggregationQuery_Aggregation::MergeFrom(const StructuredAggregati
       _internal_mutable_count()->::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count::MergeFrom(from._internal_count());
       break;
     }
+    case kSum: {
+      _internal_mutable_sum()->::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum::MergeFrom(from._internal_sum());
+      break;
+    }
+    case kAvg: {
+      _internal_mutable_avg()->::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg::MergeFrom(from._internal_avg());
+      break;
+    }
     case OPERATOR_NOT_SET: {
       break;
     }
@@ -4142,6 +4732,12 @@ template<> PROTOBUF_NOINLINE ::google::firestore::v1::StructuredQuery* Arena::Cr
 template<> PROTOBUF_NOINLINE ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count* Arena::CreateMaybeMessage< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count >(Arena* arena) {
   return Arena::CreateInternal< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count >(arena);
 }
+template<> PROTOBUF_NOINLINE ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* Arena::CreateMaybeMessage< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum >(Arena* arena) {
+  return Arena::CreateInternal< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum >(arena);
+}
+template<> PROTOBUF_NOINLINE ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* Arena::CreateMaybeMessage< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg >(Arena* arena) {
+  return Arena::CreateInternal< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg >(arena);
+}
 template<> PROTOBUF_NOINLINE ::google::firestore::v1::StructuredAggregationQuery_Aggregation* Arena::CreateMaybeMessage< ::google::firestore::v1::StructuredAggregationQuery_Aggregation >(Arena* arena) {
   return Arena::CreateInternal< ::google::firestore::v1::StructuredAggregationQuery_Aggregation >(arena);
 }

+ 552 - 4
Firestore/Protos/cpp/google/firestore/v1/query.pb.h

@@ -66,7 +66,7 @@ struct TableStruct_google_2ffirestore_2fv1_2fquery_2eproto {
     PROTOBUF_SECTION_VARIABLE(protodesc_cold);
   static const ::PROTOBUF_NAMESPACE_ID::internal::AuxillaryParseTableField aux[]
     PROTOBUF_SECTION_VARIABLE(protodesc_cold);
-  static const ::PROTOBUF_NAMESPACE_ID::internal::ParseTable schema[13]
+  static const ::PROTOBUF_NAMESPACE_ID::internal::ParseTable schema[15]
     PROTOBUF_SECTION_VARIABLE(protodesc_cold);
   static const ::PROTOBUF_NAMESPACE_ID::internal::FieldMetadata field_metadata[];
   static const ::PROTOBUF_NAMESPACE_ID::internal::SerializationTable serialization_table[];
@@ -85,9 +85,15 @@ extern StructuredAggregationQueryDefaultTypeInternal _StructuredAggregationQuery
 class StructuredAggregationQuery_Aggregation;
 class StructuredAggregationQuery_AggregationDefaultTypeInternal;
 extern StructuredAggregationQuery_AggregationDefaultTypeInternal _StructuredAggregationQuery_Aggregation_default_instance_;
+class StructuredAggregationQuery_Aggregation_Avg;
+class StructuredAggregationQuery_Aggregation_AvgDefaultTypeInternal;
+extern StructuredAggregationQuery_Aggregation_AvgDefaultTypeInternal _StructuredAggregationQuery_Aggregation_Avg_default_instance_;
 class StructuredAggregationQuery_Aggregation_Count;
 class StructuredAggregationQuery_Aggregation_CountDefaultTypeInternal;
 extern StructuredAggregationQuery_Aggregation_CountDefaultTypeInternal _StructuredAggregationQuery_Aggregation_Count_default_instance_;
+class StructuredAggregationQuery_Aggregation_Sum;
+class StructuredAggregationQuery_Aggregation_SumDefaultTypeInternal;
+extern StructuredAggregationQuery_Aggregation_SumDefaultTypeInternal _StructuredAggregationQuery_Aggregation_Sum_default_instance_;
 class StructuredQuery;
 class StructuredQueryDefaultTypeInternal;
 extern StructuredQueryDefaultTypeInternal _StructuredQuery_default_instance_;
@@ -122,7 +128,9 @@ PROTOBUF_NAMESPACE_OPEN
 template<> ::google::firestore::v1::Cursor* Arena::CreateMaybeMessage<::google::firestore::v1::Cursor>(Arena*);
 template<> ::google::firestore::v1::StructuredAggregationQuery* Arena::CreateMaybeMessage<::google::firestore::v1::StructuredAggregationQuery>(Arena*);
 template<> ::google::firestore::v1::StructuredAggregationQuery_Aggregation* Arena::CreateMaybeMessage<::google::firestore::v1::StructuredAggregationQuery_Aggregation>(Arena*);
+template<> ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* Arena::CreateMaybeMessage<::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg>(Arena*);
 template<> ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count* Arena::CreateMaybeMessage<::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count>(Arena*);
+template<> ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* Arena::CreateMaybeMessage<::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum>(Arena*);
 template<> ::google::firestore::v1::StructuredQuery* Arena::CreateMaybeMessage<::google::firestore::v1::StructuredQuery>(Arena*);
 template<> ::google::firestore::v1::StructuredQuery_CollectionSelector* Arena::CreateMaybeMessage<::google::firestore::v1::StructuredQuery_CollectionSelector>(Arena*);
 template<> ::google::firestore::v1::StructuredQuery_CompositeFilter* Arena::CreateMaybeMessage<::google::firestore::v1::StructuredQuery_CompositeFilter>(Arena*);
@@ -2019,6 +2027,274 @@ class StructuredAggregationQuery_Aggregation_Count :
 };
 // -------------------------------------------------------------------
 
+class StructuredAggregationQuery_Aggregation_Sum :
+    public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum) */ {
+ public:
+  StructuredAggregationQuery_Aggregation_Sum();
+  virtual ~StructuredAggregationQuery_Aggregation_Sum();
+
+  StructuredAggregationQuery_Aggregation_Sum(const StructuredAggregationQuery_Aggregation_Sum& from);
+  StructuredAggregationQuery_Aggregation_Sum(StructuredAggregationQuery_Aggregation_Sum&& from) noexcept
+    : StructuredAggregationQuery_Aggregation_Sum() {
+    *this = ::std::move(from);
+  }
+
+  inline StructuredAggregationQuery_Aggregation_Sum& operator=(const StructuredAggregationQuery_Aggregation_Sum& from) {
+    CopyFrom(from);
+    return *this;
+  }
+  inline StructuredAggregationQuery_Aggregation_Sum& operator=(StructuredAggregationQuery_Aggregation_Sum&& from) noexcept {
+    if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) {
+      if (this != &from) InternalSwap(&from);
+    } else {
+      CopyFrom(from);
+    }
+    return *this;
+  }
+
+  static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() {
+    return GetDescriptor();
+  }
+  static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() {
+    return GetMetadataStatic().descriptor;
+  }
+  static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() {
+    return GetMetadataStatic().reflection;
+  }
+  static const StructuredAggregationQuery_Aggregation_Sum& default_instance();
+
+  static void InitAsDefaultInstance();  // FOR INTERNAL USE ONLY
+  static inline const StructuredAggregationQuery_Aggregation_Sum* internal_default_instance() {
+    return reinterpret_cast<const StructuredAggregationQuery_Aggregation_Sum*>(
+               &_StructuredAggregationQuery_Aggregation_Sum_default_instance_);
+  }
+  static constexpr int kIndexInFileMessages =
+    10;
+
+  friend void swap(StructuredAggregationQuery_Aggregation_Sum& a, StructuredAggregationQuery_Aggregation_Sum& b) {
+    a.Swap(&b);
+  }
+  inline void Swap(StructuredAggregationQuery_Aggregation_Sum* other) {
+    if (other == this) return;
+    InternalSwap(other);
+  }
+
+  // implements Message ----------------------------------------------
+
+  inline StructuredAggregationQuery_Aggregation_Sum* New() const final {
+    return CreateMaybeMessage<StructuredAggregationQuery_Aggregation_Sum>(nullptr);
+  }
+
+  StructuredAggregationQuery_Aggregation_Sum* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final {
+    return CreateMaybeMessage<StructuredAggregationQuery_Aggregation_Sum>(arena);
+  }
+  void CopyFrom(const ::PROTOBUF_NAMESPACE_ID::Message& from) final;
+  void MergeFrom(const ::PROTOBUF_NAMESPACE_ID::Message& from) final;
+  void CopyFrom(const StructuredAggregationQuery_Aggregation_Sum& from);
+  void MergeFrom(const StructuredAggregationQuery_Aggregation_Sum& from);
+  PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final;
+  bool IsInitialized() const final;
+
+  size_t ByteSizeLong() const final;
+  const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final;
+  ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize(
+      ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final;
+  int GetCachedSize() const final { return _cached_size_.Get(); }
+
+  private:
+  inline void SharedCtor();
+  inline void SharedDtor();
+  void SetCachedSize(int size) const final;
+  void InternalSwap(StructuredAggregationQuery_Aggregation_Sum* other);
+  friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata;
+  static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() {
+    return "google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum";
+  }
+  private:
+  inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const {
+    return nullptr;
+  }
+  inline void* MaybeArenaPtr() const {
+    return nullptr;
+  }
+  public:
+
+  ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final;
+  private:
+  static ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadataStatic() {
+    ::PROTOBUF_NAMESPACE_ID::internal::AssignDescriptors(&::descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto);
+    return ::descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto.file_level_metadata[kIndexInFileMessages];
+  }
+
+  public:
+
+  // nested types ----------------------------------------------------
+
+  // accessors -------------------------------------------------------
+
+  enum : int {
+    kFieldFieldNumber = 1,
+  };
+  // .google.firestore.v1.StructuredQuery.FieldReference field = 1;
+  bool has_field() const;
+  private:
+  bool _internal_has_field() const;
+  public:
+  void clear_field();
+  const ::google::firestore::v1::StructuredQuery_FieldReference& field() const;
+  ::google::firestore::v1::StructuredQuery_FieldReference* release_field();
+  ::google::firestore::v1::StructuredQuery_FieldReference* mutable_field();
+  void set_allocated_field(::google::firestore::v1::StructuredQuery_FieldReference* field);
+  private:
+  const ::google::firestore::v1::StructuredQuery_FieldReference& _internal_field() const;
+  ::google::firestore::v1::StructuredQuery_FieldReference* _internal_mutable_field();
+  public:
+
+  // @@protoc_insertion_point(class_scope:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum)
+ private:
+  class _Internal;
+
+  ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArena _internal_metadata_;
+  ::google::firestore::v1::StructuredQuery_FieldReference* field_;
+  mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_;
+  friend struct ::TableStruct_google_2ffirestore_2fv1_2fquery_2eproto;
+};
+// -------------------------------------------------------------------
+
+class StructuredAggregationQuery_Aggregation_Avg :
+    public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg) */ {
+ public:
+  StructuredAggregationQuery_Aggregation_Avg();
+  virtual ~StructuredAggregationQuery_Aggregation_Avg();
+
+  StructuredAggregationQuery_Aggregation_Avg(const StructuredAggregationQuery_Aggregation_Avg& from);
+  StructuredAggregationQuery_Aggregation_Avg(StructuredAggregationQuery_Aggregation_Avg&& from) noexcept
+    : StructuredAggregationQuery_Aggregation_Avg() {
+    *this = ::std::move(from);
+  }
+
+  inline StructuredAggregationQuery_Aggregation_Avg& operator=(const StructuredAggregationQuery_Aggregation_Avg& from) {
+    CopyFrom(from);
+    return *this;
+  }
+  inline StructuredAggregationQuery_Aggregation_Avg& operator=(StructuredAggregationQuery_Aggregation_Avg&& from) noexcept {
+    if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) {
+      if (this != &from) InternalSwap(&from);
+    } else {
+      CopyFrom(from);
+    }
+    return *this;
+  }
+
+  static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() {
+    return GetDescriptor();
+  }
+  static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() {
+    return GetMetadataStatic().descriptor;
+  }
+  static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() {
+    return GetMetadataStatic().reflection;
+  }
+  static const StructuredAggregationQuery_Aggregation_Avg& default_instance();
+
+  static void InitAsDefaultInstance();  // FOR INTERNAL USE ONLY
+  static inline const StructuredAggregationQuery_Aggregation_Avg* internal_default_instance() {
+    return reinterpret_cast<const StructuredAggregationQuery_Aggregation_Avg*>(
+               &_StructuredAggregationQuery_Aggregation_Avg_default_instance_);
+  }
+  static constexpr int kIndexInFileMessages =
+    11;
+
+  friend void swap(StructuredAggregationQuery_Aggregation_Avg& a, StructuredAggregationQuery_Aggregation_Avg& b) {
+    a.Swap(&b);
+  }
+  inline void Swap(StructuredAggregationQuery_Aggregation_Avg* other) {
+    if (other == this) return;
+    InternalSwap(other);
+  }
+
+  // implements Message ----------------------------------------------
+
+  inline StructuredAggregationQuery_Aggregation_Avg* New() const final {
+    return CreateMaybeMessage<StructuredAggregationQuery_Aggregation_Avg>(nullptr);
+  }
+
+  StructuredAggregationQuery_Aggregation_Avg* New(::PROTOBUF_NAMESPACE_ID::Arena* arena) const final {
+    return CreateMaybeMessage<StructuredAggregationQuery_Aggregation_Avg>(arena);
+  }
+  void CopyFrom(const ::PROTOBUF_NAMESPACE_ID::Message& from) final;
+  void MergeFrom(const ::PROTOBUF_NAMESPACE_ID::Message& from) final;
+  void CopyFrom(const StructuredAggregationQuery_Aggregation_Avg& from);
+  void MergeFrom(const StructuredAggregationQuery_Aggregation_Avg& from);
+  PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final;
+  bool IsInitialized() const final;
+
+  size_t ByteSizeLong() const final;
+  const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final;
+  ::PROTOBUF_NAMESPACE_ID::uint8* _InternalSerialize(
+      ::PROTOBUF_NAMESPACE_ID::uint8* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final;
+  int GetCachedSize() const final { return _cached_size_.Get(); }
+
+  private:
+  inline void SharedCtor();
+  inline void SharedDtor();
+  void SetCachedSize(int size) const final;
+  void InternalSwap(StructuredAggregationQuery_Aggregation_Avg* other);
+  friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata;
+  static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() {
+    return "google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg";
+  }
+  private:
+  inline ::PROTOBUF_NAMESPACE_ID::Arena* GetArenaNoVirtual() const {
+    return nullptr;
+  }
+  inline void* MaybeArenaPtr() const {
+    return nullptr;
+  }
+  public:
+
+  ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final;
+  private:
+  static ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadataStatic() {
+    ::PROTOBUF_NAMESPACE_ID::internal::AssignDescriptors(&::descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto);
+    return ::descriptor_table_google_2ffirestore_2fv1_2fquery_2eproto.file_level_metadata[kIndexInFileMessages];
+  }
+
+  public:
+
+  // nested types ----------------------------------------------------
+
+  // accessors -------------------------------------------------------
+
+  enum : int {
+    kFieldFieldNumber = 1,
+  };
+  // .google.firestore.v1.StructuredQuery.FieldReference field = 1;
+  bool has_field() const;
+  private:
+  bool _internal_has_field() const;
+  public:
+  void clear_field();
+  const ::google::firestore::v1::StructuredQuery_FieldReference& field() const;
+  ::google::firestore::v1::StructuredQuery_FieldReference* release_field();
+  ::google::firestore::v1::StructuredQuery_FieldReference* mutable_field();
+  void set_allocated_field(::google::firestore::v1::StructuredQuery_FieldReference* field);
+  private:
+  const ::google::firestore::v1::StructuredQuery_FieldReference& _internal_field() const;
+  ::google::firestore::v1::StructuredQuery_FieldReference* _internal_mutable_field();
+  public:
+
+  // @@protoc_insertion_point(class_scope:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg)
+ private:
+  class _Internal;
+
+  ::PROTOBUF_NAMESPACE_ID::internal::InternalMetadataWithArena _internal_metadata_;
+  ::google::firestore::v1::StructuredQuery_FieldReference* field_;
+  mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_;
+  friend struct ::TableStruct_google_2ffirestore_2fv1_2fquery_2eproto;
+};
+// -------------------------------------------------------------------
+
 class StructuredAggregationQuery_Aggregation :
     public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:google.firestore.v1.StructuredAggregationQuery.Aggregation) */ {
  public:
@@ -2057,6 +2333,8 @@ class StructuredAggregationQuery_Aggregation :
 
   enum OperatorCase {
     kCount = 1,
+    kSum = 2,
+    kAvg = 3,
     OPERATOR_NOT_SET = 0,
   };
 
@@ -2066,7 +2344,7 @@ class StructuredAggregationQuery_Aggregation :
                &_StructuredAggregationQuery_Aggregation_default_instance_);
   }
   static constexpr int kIndexInFileMessages =
-    10;
+    12;
 
   friend void swap(StructuredAggregationQuery_Aggregation& a, StructuredAggregationQuery_Aggregation& b) {
     a.Swap(&b);
@@ -2128,12 +2406,16 @@ class StructuredAggregationQuery_Aggregation :
   // nested types ----------------------------------------------------
 
   typedef StructuredAggregationQuery_Aggregation_Count Count;
+  typedef StructuredAggregationQuery_Aggregation_Sum Sum;
+  typedef StructuredAggregationQuery_Aggregation_Avg Avg;
 
   // accessors -------------------------------------------------------
 
   enum : int {
     kAliasFieldNumber = 7,
     kCountFieldNumber = 1,
+    kSumFieldNumber = 2,
+    kAvgFieldNumber = 3,
   };
   // string alias = 7;
   void clear_alias();
@@ -2166,12 +2448,44 @@ class StructuredAggregationQuery_Aggregation :
   ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count* _internal_mutable_count();
   public:
 
+  // .google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum sum = 2;
+  bool has_sum() const;
+  private:
+  bool _internal_has_sum() const;
+  public:
+  void clear_sum();
+  const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum& sum() const;
+  ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* release_sum();
+  ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* mutable_sum();
+  void set_allocated_sum(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* sum);
+  private:
+  const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum& _internal_sum() const;
+  ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* _internal_mutable_sum();
+  public:
+
+  // .google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg avg = 3;
+  bool has_avg() const;
+  private:
+  bool _internal_has_avg() const;
+  public:
+  void clear_avg();
+  const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg& avg() const;
+  ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* release_avg();
+  ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* mutable_avg();
+  void set_allocated_avg(::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* avg);
+  private:
+  const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg& _internal_avg() const;
+  ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* _internal_mutable_avg();
+  public:
+
   void clear_operator();
   OperatorCase operator_case() const;
   // @@protoc_insertion_point(class_scope:google.firestore.v1.StructuredAggregationQuery.Aggregation)
  private:
   class _Internal;
   void set_has_count();
+  void set_has_sum();
+  void set_has_avg();
 
   inline bool has_operator() const;
   inline void clear_has_operator();
@@ -2181,6 +2495,8 @@ class StructuredAggregationQuery_Aggregation :
   union OperatorUnion {
     OperatorUnion() {}
     ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count* count_;
+    ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* sum_;
+    ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* avg_;
   } operator_;
   mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_;
   ::PROTOBUF_NAMESPACE_ID::uint32 _oneof_case_[1];
@@ -2236,7 +2552,7 @@ class StructuredAggregationQuery :
                &_StructuredAggregationQuery_default_instance_);
   }
   static constexpr int kIndexInFileMessages =
-    11;
+    13;
 
   friend void swap(StructuredAggregationQuery& a, StructuredAggregationQuery& b) {
     a.Swap(&b);
@@ -2403,7 +2719,7 @@ class Cursor :
                &_Cursor_default_instance_);
   }
   static constexpr int kIndexInFileMessages =
-    12;
+    14;
 
   friend void swap(Cursor& a, Cursor& b) {
     a.Swap(&b);
@@ -3694,6 +4010,134 @@ inline void StructuredAggregationQuery_Aggregation_Count::set_allocated_up_to(PR
 
 // -------------------------------------------------------------------
 
+// StructuredAggregationQuery_Aggregation_Sum
+
+// .google.firestore.v1.StructuredQuery.FieldReference field = 1;
+inline bool StructuredAggregationQuery_Aggregation_Sum::_internal_has_field() const {
+  return this != internal_default_instance() && field_ != nullptr;
+}
+inline bool StructuredAggregationQuery_Aggregation_Sum::has_field() const {
+  return _internal_has_field();
+}
+inline void StructuredAggregationQuery_Aggregation_Sum::clear_field() {
+  if (GetArenaNoVirtual() == nullptr && field_ != nullptr) {
+    delete field_;
+  }
+  field_ = nullptr;
+}
+inline const ::google::firestore::v1::StructuredQuery_FieldReference& StructuredAggregationQuery_Aggregation_Sum::_internal_field() const {
+  const ::google::firestore::v1::StructuredQuery_FieldReference* p = field_;
+  return p != nullptr ? *p : *reinterpret_cast<const ::google::firestore::v1::StructuredQuery_FieldReference*>(
+      &::google::firestore::v1::_StructuredQuery_FieldReference_default_instance_);
+}
+inline const ::google::firestore::v1::StructuredQuery_FieldReference& StructuredAggregationQuery_Aggregation_Sum::field() const {
+  // @@protoc_insertion_point(field_get:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum.field)
+  return _internal_field();
+}
+inline ::google::firestore::v1::StructuredQuery_FieldReference* StructuredAggregationQuery_Aggregation_Sum::release_field() {
+  // @@protoc_insertion_point(field_release:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum.field)
+  
+  ::google::firestore::v1::StructuredQuery_FieldReference* temp = field_;
+  field_ = nullptr;
+  return temp;
+}
+inline ::google::firestore::v1::StructuredQuery_FieldReference* StructuredAggregationQuery_Aggregation_Sum::_internal_mutable_field() {
+  
+  if (field_ == nullptr) {
+    auto* p = CreateMaybeMessage<::google::firestore::v1::StructuredQuery_FieldReference>(GetArenaNoVirtual());
+    field_ = p;
+  }
+  return field_;
+}
+inline ::google::firestore::v1::StructuredQuery_FieldReference* StructuredAggregationQuery_Aggregation_Sum::mutable_field() {
+  // @@protoc_insertion_point(field_mutable:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum.field)
+  return _internal_mutable_field();
+}
+inline void StructuredAggregationQuery_Aggregation_Sum::set_allocated_field(::google::firestore::v1::StructuredQuery_FieldReference* field) {
+  ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual();
+  if (message_arena == nullptr) {
+    delete field_;
+  }
+  if (field) {
+    ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr;
+    if (message_arena != submessage_arena) {
+      field = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage(
+          message_arena, field, submessage_arena);
+    }
+    
+  } else {
+    
+  }
+  field_ = field;
+  // @@protoc_insertion_point(field_set_allocated:google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum.field)
+}
+
+// -------------------------------------------------------------------
+
+// StructuredAggregationQuery_Aggregation_Avg
+
+// .google.firestore.v1.StructuredQuery.FieldReference field = 1;
+inline bool StructuredAggregationQuery_Aggregation_Avg::_internal_has_field() const {
+  return this != internal_default_instance() && field_ != nullptr;
+}
+inline bool StructuredAggregationQuery_Aggregation_Avg::has_field() const {
+  return _internal_has_field();
+}
+inline void StructuredAggregationQuery_Aggregation_Avg::clear_field() {
+  if (GetArenaNoVirtual() == nullptr && field_ != nullptr) {
+    delete field_;
+  }
+  field_ = nullptr;
+}
+inline const ::google::firestore::v1::StructuredQuery_FieldReference& StructuredAggregationQuery_Aggregation_Avg::_internal_field() const {
+  const ::google::firestore::v1::StructuredQuery_FieldReference* p = field_;
+  return p != nullptr ? *p : *reinterpret_cast<const ::google::firestore::v1::StructuredQuery_FieldReference*>(
+      &::google::firestore::v1::_StructuredQuery_FieldReference_default_instance_);
+}
+inline const ::google::firestore::v1::StructuredQuery_FieldReference& StructuredAggregationQuery_Aggregation_Avg::field() const {
+  // @@protoc_insertion_point(field_get:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg.field)
+  return _internal_field();
+}
+inline ::google::firestore::v1::StructuredQuery_FieldReference* StructuredAggregationQuery_Aggregation_Avg::release_field() {
+  // @@protoc_insertion_point(field_release:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg.field)
+  
+  ::google::firestore::v1::StructuredQuery_FieldReference* temp = field_;
+  field_ = nullptr;
+  return temp;
+}
+inline ::google::firestore::v1::StructuredQuery_FieldReference* StructuredAggregationQuery_Aggregation_Avg::_internal_mutable_field() {
+  
+  if (field_ == nullptr) {
+    auto* p = CreateMaybeMessage<::google::firestore::v1::StructuredQuery_FieldReference>(GetArenaNoVirtual());
+    field_ = p;
+  }
+  return field_;
+}
+inline ::google::firestore::v1::StructuredQuery_FieldReference* StructuredAggregationQuery_Aggregation_Avg::mutable_field() {
+  // @@protoc_insertion_point(field_mutable:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg.field)
+  return _internal_mutable_field();
+}
+inline void StructuredAggregationQuery_Aggregation_Avg::set_allocated_field(::google::firestore::v1::StructuredQuery_FieldReference* field) {
+  ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaNoVirtual();
+  if (message_arena == nullptr) {
+    delete field_;
+  }
+  if (field) {
+    ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = nullptr;
+    if (message_arena != submessage_arena) {
+      field = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage(
+          message_arena, field, submessage_arena);
+    }
+    
+  } else {
+    
+  }
+  field_ = field;
+  // @@protoc_insertion_point(field_set_allocated:google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg.field)
+}
+
+// -------------------------------------------------------------------
+
 // StructuredAggregationQuery_Aggregation
 
 // .google.firestore.v1.StructuredAggregationQuery.Aggregation.Count count = 1;
@@ -3746,6 +4190,106 @@ inline ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Count* St
   return _internal_mutable_count();
 }
 
+// .google.firestore.v1.StructuredAggregationQuery.Aggregation.Sum sum = 2;
+inline bool StructuredAggregationQuery_Aggregation::_internal_has_sum() const {
+  return operator_case() == kSum;
+}
+inline bool StructuredAggregationQuery_Aggregation::has_sum() const {
+  return _internal_has_sum();
+}
+inline void StructuredAggregationQuery_Aggregation::set_has_sum() {
+  _oneof_case_[0] = kSum;
+}
+inline void StructuredAggregationQuery_Aggregation::clear_sum() {
+  if (_internal_has_sum()) {
+    delete operator_.sum_;
+    clear_has_operator();
+  }
+}
+inline ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* StructuredAggregationQuery_Aggregation::release_sum() {
+  // @@protoc_insertion_point(field_release:google.firestore.v1.StructuredAggregationQuery.Aggregation.sum)
+  if (_internal_has_sum()) {
+    clear_has_operator();
+      ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* temp = operator_.sum_;
+    operator_.sum_ = nullptr;
+    return temp;
+  } else {
+    return nullptr;
+  }
+}
+inline const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum& StructuredAggregationQuery_Aggregation::_internal_sum() const {
+  return _internal_has_sum()
+      ? *operator_.sum_
+      : *reinterpret_cast< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum*>(&::google::firestore::v1::_StructuredAggregationQuery_Aggregation_Sum_default_instance_);
+}
+inline const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum& StructuredAggregationQuery_Aggregation::sum() const {
+  // @@protoc_insertion_point(field_get:google.firestore.v1.StructuredAggregationQuery.Aggregation.sum)
+  return _internal_sum();
+}
+inline ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* StructuredAggregationQuery_Aggregation::_internal_mutable_sum() {
+  if (!_internal_has_sum()) {
+    clear_operator();
+    set_has_sum();
+    operator_.sum_ = CreateMaybeMessage< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum >(
+        GetArenaNoVirtual());
+  }
+  return operator_.sum_;
+}
+inline ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Sum* StructuredAggregationQuery_Aggregation::mutable_sum() {
+  // @@protoc_insertion_point(field_mutable:google.firestore.v1.StructuredAggregationQuery.Aggregation.sum)
+  return _internal_mutable_sum();
+}
+
+// .google.firestore.v1.StructuredAggregationQuery.Aggregation.Avg avg = 3;
+inline bool StructuredAggregationQuery_Aggregation::_internal_has_avg() const {
+  return operator_case() == kAvg;
+}
+inline bool StructuredAggregationQuery_Aggregation::has_avg() const {
+  return _internal_has_avg();
+}
+inline void StructuredAggregationQuery_Aggregation::set_has_avg() {
+  _oneof_case_[0] = kAvg;
+}
+inline void StructuredAggregationQuery_Aggregation::clear_avg() {
+  if (_internal_has_avg()) {
+    delete operator_.avg_;
+    clear_has_operator();
+  }
+}
+inline ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* StructuredAggregationQuery_Aggregation::release_avg() {
+  // @@protoc_insertion_point(field_release:google.firestore.v1.StructuredAggregationQuery.Aggregation.avg)
+  if (_internal_has_avg()) {
+    clear_has_operator();
+      ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* temp = operator_.avg_;
+    operator_.avg_ = nullptr;
+    return temp;
+  } else {
+    return nullptr;
+  }
+}
+inline const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg& StructuredAggregationQuery_Aggregation::_internal_avg() const {
+  return _internal_has_avg()
+      ? *operator_.avg_
+      : *reinterpret_cast< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg*>(&::google::firestore::v1::_StructuredAggregationQuery_Aggregation_Avg_default_instance_);
+}
+inline const ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg& StructuredAggregationQuery_Aggregation::avg() const {
+  // @@protoc_insertion_point(field_get:google.firestore.v1.StructuredAggregationQuery.Aggregation.avg)
+  return _internal_avg();
+}
+inline ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* StructuredAggregationQuery_Aggregation::_internal_mutable_avg() {
+  if (!_internal_has_avg()) {
+    clear_operator();
+    set_has_avg();
+    operator_.avg_ = CreateMaybeMessage< ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg >(
+        GetArenaNoVirtual());
+  }
+  return operator_.avg_;
+}
+inline ::google::firestore::v1::StructuredAggregationQuery_Aggregation_Avg* StructuredAggregationQuery_Aggregation::mutable_avg() {
+  // @@protoc_insertion_point(field_mutable:google.firestore.v1.StructuredAggregationQuery.Aggregation.avg)
+  return _internal_mutable_avg();
+}
+
 // string alias = 7;
 inline void StructuredAggregationQuery_Aggregation::clear_alias() {
   alias_.ClearToEmptyNoArena(&::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited());
@@ -4004,6 +4548,10 @@ inline void Cursor::set_before(bool value) {
 
 // -------------------------------------------------------------------
 
+// -------------------------------------------------------------------
+
+// -------------------------------------------------------------------
+
 
 // @@protoc_insertion_point(namespace_scope)
 

File diff suppressed because it is too large
+ 14 - 3
Firestore/Protos/nanopb/google/firestore/v1/query.nanopb.cc


+ 29 - 1
Firestore/Protos/nanopb/google/firestore/v1/query.nanopb.h

@@ -108,6 +108,13 @@ typedef struct _google_firestore_v1_Cursor {
 /* @@protoc_insertion_point(struct:google_firestore_v1_Cursor) */
 } google_firestore_v1_Cursor;
 
+typedef struct _google_firestore_v1_StructuredAggregationQuery_Aggregation_Avg {
+    google_firestore_v1_StructuredQuery_FieldReference field;
+
+    std::string ToString(int indent = 0) const;
+/* @@protoc_insertion_point(struct:google_firestore_v1_StructuredAggregationQuery_Aggregation_Avg) */
+} google_firestore_v1_StructuredAggregationQuery_Aggregation_Avg;
+
 typedef struct _google_firestore_v1_StructuredAggregationQuery_Aggregation_Count {
     google_protobuf_Int64Value up_to;
 
@@ -115,6 +122,13 @@ typedef struct _google_firestore_v1_StructuredAggregationQuery_Aggregation_Count
 /* @@protoc_insertion_point(struct:google_firestore_v1_StructuredAggregationQuery_Aggregation_Count) */
 } google_firestore_v1_StructuredAggregationQuery_Aggregation_Count;
 
+typedef struct _google_firestore_v1_StructuredAggregationQuery_Aggregation_Sum {
+    google_firestore_v1_StructuredQuery_FieldReference field;
+
+    std::string ToString(int indent = 0) const;
+/* @@protoc_insertion_point(struct:google_firestore_v1_StructuredAggregationQuery_Aggregation_Sum) */
+} google_firestore_v1_StructuredAggregationQuery_Aggregation_Sum;
+
 typedef struct _google_firestore_v1_StructuredQuery_CollectionSelector {
     pb_bytes_array_t *collection_id;
     bool all_descendants;
@@ -164,6 +178,8 @@ typedef struct _google_firestore_v1_StructuredAggregationQuery_Aggregation {
     pb_size_t which_operator;
     union {
         google_firestore_v1_StructuredAggregationQuery_Aggregation_Count count;
+        google_firestore_v1_StructuredAggregationQuery_Aggregation_Sum sum;
+        google_firestore_v1_StructuredAggregationQuery_Aggregation_Avg avg;
     };
     pb_bytes_array_t *alias;
 
@@ -227,6 +243,8 @@ typedef struct _google_firestore_v1_StructuredAggregationQuery {
 #define google_firestore_v1_StructuredAggregationQuery_init_default {0, {google_firestore_v1_StructuredQuery_init_default}, 0, NULL}
 #define google_firestore_v1_StructuredAggregationQuery_Aggregation_init_default {0, {google_firestore_v1_StructuredAggregationQuery_Aggregation_Count_init_default}, NULL}
 #define google_firestore_v1_StructuredAggregationQuery_Aggregation_Count_init_default {google_protobuf_Int64Value_init_default}
+#define google_firestore_v1_StructuredAggregationQuery_Aggregation_Sum_init_default {google_firestore_v1_StructuredQuery_FieldReference_init_default}
+#define google_firestore_v1_StructuredAggregationQuery_Aggregation_Avg_init_default {google_firestore_v1_StructuredQuery_FieldReference_init_default}
 #define google_firestore_v1_Cursor_init_default  {0, NULL, 0}
 #define google_firestore_v1_StructuredQuery_init_zero {google_firestore_v1_StructuredQuery_Projection_init_zero, 0, NULL, google_firestore_v1_StructuredQuery_Filter_init_zero, 0, NULL, false, google_protobuf_Int32Value_init_zero, 0, google_firestore_v1_Cursor_init_zero, google_firestore_v1_Cursor_init_zero}
 #define google_firestore_v1_StructuredQuery_CollectionSelector_init_zero {NULL, 0}
@@ -240,6 +258,8 @@ typedef struct _google_firestore_v1_StructuredAggregationQuery {
 #define google_firestore_v1_StructuredAggregationQuery_init_zero {0, {google_firestore_v1_StructuredQuery_init_zero}, 0, NULL}
 #define google_firestore_v1_StructuredAggregationQuery_Aggregation_init_zero {0, {google_firestore_v1_StructuredAggregationQuery_Aggregation_Count_init_zero}, NULL}
 #define google_firestore_v1_StructuredAggregationQuery_Aggregation_Count_init_zero {google_protobuf_Int64Value_init_zero}
+#define google_firestore_v1_StructuredAggregationQuery_Aggregation_Sum_init_zero {google_firestore_v1_StructuredQuery_FieldReference_init_zero}
+#define google_firestore_v1_StructuredAggregationQuery_Aggregation_Avg_init_zero {google_firestore_v1_StructuredQuery_FieldReference_init_zero}
 #define google_firestore_v1_Cursor_init_zero     {0, NULL, 0}
 
 /* Field tags (for use in manual encoding/decoding) */
@@ -247,7 +267,9 @@ typedef struct _google_firestore_v1_StructuredAggregationQuery {
 #define google_firestore_v1_StructuredQuery_Projection_fields_tag 2
 #define google_firestore_v1_Cursor_values_tag    1
 #define google_firestore_v1_Cursor_before_tag    2
+#define google_firestore_v1_StructuredAggregationQuery_Aggregation_Avg_field_tag 1
 #define google_firestore_v1_StructuredAggregationQuery_Aggregation_Count_up_to_tag 1
+#define google_firestore_v1_StructuredAggregationQuery_Aggregation_Sum_field_tag 1
 #define google_firestore_v1_StructuredQuery_CollectionSelector_collection_id_tag 2
 #define google_firestore_v1_StructuredQuery_CollectionSelector_all_descendants_tag 3
 #define google_firestore_v1_StructuredQuery_CompositeFilter_op_tag 1
@@ -260,6 +282,8 @@ typedef struct _google_firestore_v1_StructuredAggregationQuery {
 #define google_firestore_v1_StructuredQuery_UnaryFilter_field_tag 2
 #define google_firestore_v1_StructuredQuery_UnaryFilter_op_tag 1
 #define google_firestore_v1_StructuredAggregationQuery_Aggregation_count_tag 1
+#define google_firestore_v1_StructuredAggregationQuery_Aggregation_sum_tag 2
+#define google_firestore_v1_StructuredAggregationQuery_Aggregation_avg_tag 3
 #define google_firestore_v1_StructuredAggregationQuery_Aggregation_alias_tag 7
 #define google_firestore_v1_StructuredQuery_Filter_composite_filter_tag 1
 #define google_firestore_v1_StructuredQuery_Filter_field_filter_tag 2
@@ -286,8 +310,10 @@ extern const pb_field_t google_firestore_v1_StructuredQuery_Order_fields[3];
 extern const pb_field_t google_firestore_v1_StructuredQuery_FieldReference_fields[2];
 extern const pb_field_t google_firestore_v1_StructuredQuery_Projection_fields[2];
 extern const pb_field_t google_firestore_v1_StructuredAggregationQuery_fields[3];
-extern const pb_field_t google_firestore_v1_StructuredAggregationQuery_Aggregation_fields[3];
+extern const pb_field_t google_firestore_v1_StructuredAggregationQuery_Aggregation_fields[5];
 extern const pb_field_t google_firestore_v1_StructuredAggregationQuery_Aggregation_Count_fields[2];
+extern const pb_field_t google_firestore_v1_StructuredAggregationQuery_Aggregation_Sum_fields[2];
+extern const pb_field_t google_firestore_v1_StructuredAggregationQuery_Aggregation_Avg_fields[2];
 extern const pb_field_t google_firestore_v1_Cursor_fields[3];
 
 /* Maximum encoded size of messages (where known) */
@@ -303,6 +329,8 @@ extern const pb_field_t google_firestore_v1_Cursor_fields[3];
 /* google_firestore_v1_StructuredAggregationQuery_size depends on runtime parameters */
 /* google_firestore_v1_StructuredAggregationQuery_Aggregation_size depends on runtime parameters */
 #define google_firestore_v1_StructuredAggregationQuery_Aggregation_Count_size 13
+/* google_firestore_v1_StructuredAggregationQuery_Aggregation_Sum_size depends on runtime parameters */
+/* google_firestore_v1_StructuredAggregationQuery_Aggregation_Avg_size depends on runtime parameters */
 /* google_firestore_v1_Cursor_size depends on runtime parameters */
 
 /* Message IDs (where set with "msgid" option) */

+ 46 - 0
Firestore/Protos/protos/google/firestore/v1/query.proto

@@ -315,10 +315,56 @@ message StructuredAggregationQuery {
       google.protobuf.Int64Value up_to = 1;
     }
 
+    // Sum of the values of the requested field.
+    //
+    // * Only numeric values will be aggregated. All non-numeric values
+    // including `NULL` are skipped.
+    //
+    // * If the aggregated values contain `NaN`, returns `NaN`.
+    //
+    // * If the aggregated value set is empty, returns 0.
+    //
+    // * Returns a 64-bit integer if the sum result is an integer value and does
+    // not overflow. Otherwise, the result is returned as a double. Note that
+    // even if all the aggregated values are integers, the result is returned
+    // as a double if it cannot fit within a 64-bit signed integer. When this
+    // occurs, the returned value will lose precision.
+    //
+    // * When underflow occurs, floating-point aggregation is non-deterministic.
+    // This means that running the same query repeatedly without any changes to
+    // the underlying values could produce slightly different results each
+    // time. In those cases, values should be stored as integers over
+    // floating-point numbers.
+    message Sum {
+      // The field to aggregate on.
+      StructuredQuery.FieldReference field = 1;
+    }
+
+    // Average of the values of the requested field.
+    //
+    // * Only numeric values will be aggregated. All non-numeric values
+    // including `NULL` are skipped.
+    //
+    // * If the aggregated values contain `NaN`, returns `NaN`.
+    //
+    // * If the aggregated value set is empty, returns `NULL`.
+    //
+    // * Always returns the result as a double.
+    message Avg {
+      // The field to aggregate on.
+      StructuredQuery.FieldReference field = 1;
+    }
+
     // The type of aggregation to perform, required.
     oneof operator {
       // Count aggregator.
       Count count = 1;
+
+      // Sum aggregator.
+      Sum sum = 2;
+
+      // Average aggregator.
+      Avg avg = 3;
     }
 
     // Required. The name of the field to store the result of the aggregation into.

+ 62 - 0
Firestore/Source/API/FIRAggregateField+Internal.h

@@ -0,0 +1,62 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRAggregateField.h"
+#import "FIRFieldPath.h"
+
+#include <string>
+#include "Firestore/core/src/model/aggregate_alias.h"
+#include "Firestore/core/src/model/aggregate_field.h"
+
+namespace model = firebase::firestore::model;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FIRAggregateField (Internal)
+- (model::AggregateField)createInternalValue;
+- (model::AggregateAlias)createAlias;
+- (const std::string)name;
+- (const FIRFieldPath *)fieldPath;
+@end
+
+/**
+ * FIRAggregateField class for sum aggregations. Exposed internally so code can do isKindOfClass
+ * checks on it.
+ */
+@interface FSTSumAggregateField : FIRAggregateField
+- (instancetype)init NS_UNAVAILABLE;
+- (id)initWithFieldPath:(FIRFieldPath *)fieldPath;
+@end
+
+/**
+ * FIRAggregateField class for average aggregations. Exposed internally so code can do isKindOfClass
+ * checks on it.
+ */
+@interface FSTAverageAggregateField : FIRAggregateField
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithFieldPath:(FIRFieldPath *)fieldPath;
+@end
+
+/**
+ * FIRAggregateField class for count aggregations. Exposed internally so code can do isKindOfClass
+ * checks on it.
+ */
+@interface FSTCountAggregateField : FIRAggregateField
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initPrivate;
+@end
+
+NS_ASSUME_NONNULL_END

+ 116 - 0
Firestore/Source/API/FIRAggregateField.h

@@ -0,0 +1,116 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class FIRFieldPath;
+
+// TODO(sum/avg) move this entire file to ../Public/FirebaseFirestore when the API can be public
+
+/**
+ * Represents an aggregation that can be performed by Firestore.
+ */
+NS_SWIFT_NAME(AggregateField)
+@interface FIRAggregateField : NSObject
+
+/** :nodoc: */
+- (instancetype)init NS_UNAVAILABLE;
+
+/**
+ * Create an `AggregateField` object that can be used to compute the count of
+ * documents in the result set of a query.
+ *
+ * The result of a count operation will always be a 64-bit integer value.
+ *
+ * @return `AggregateField` object that can be used to compute the count of
+ * documents in the result set of a query.
+ */
++ (instancetype)aggregateFieldForCount NS_SWIFT_NAME(count());
+
+/**
+ * Create an `AggregateField` object that can be used to compute the sum of
+ * a specified field over a range of documents in the result set of a query.
+ *
+ * The result of a sum operation will always be a 64-bit integer value, a double, or NaN.
+ *
+ * - Summing over zero documents or fields will result in 0L.
+ * - Summing over NaN will result in a double value representing NaN.
+ * - A sum that overflows the maximum representable 64-bit integer value will result in a double
+ * return value. This may result in lost precision of the result.
+ * - A sum that overflows the maximum representable double value will result in a double return
+ * value representing infinity.
+ *
+ * @param field Specifies the field to sum across the result set.
+ * @return `AggregateField` object that can be used to compute the sum of
+ * a specified field over a range of documents in the result set of a query.
+ */
++ (instancetype)aggregateFieldForSumOfField:(NSString *)field NS_SWIFT_NAME(sum(_:));
+
+/**
+ * Create an `AggregateField` object that can be used to compute the sum of
+ * a specified field over a range of documents in the result set of a query.
+ *
+ * The result of a sum operation will always be a 64-bit integer value, a double, or NaN.
+ *
+ * - Summing over zero documents or fields will result in 0L.
+ * - Summing over NaN will result in a double value representing NaN.
+ * - A sum that overflows the maximum representable 64-bit integer value will result in a double
+ * return value. This may result in lost precision of the result.
+ * - A sum that overflows the maximum representable double value will result in a double return
+ * value representing infinity.
+ *
+ * @param fieldPath Specifies the field to sum across the result set.
+ * @return `AggregateField` object that can be used to compute the sum of
+ * a specified field over a range of documents in the result set of a query.
+ */
++ (instancetype)aggregateFieldForSumOfFieldPath:(FIRFieldPath *)fieldPath NS_SWIFT_NAME(sum(_:));
+
+/**
+ * Create an `AggregateField` object that can be used to compute the average of
+ * a specified field over a range of documents in the result set of a query.
+ *
+ * The result of an average operation will always be a 64-bit integer value, a double, or NaN.
+ *
+ * - Averaging over zero documents or fields will result in a double value representing NaN.
+ * - Averaging over NaN will result in a double value representing NaN.
+ *
+ * @param field Specifies the field to average across the result set.
+ * @return `AggregateField` object that can be used to compute the average of
+ * a specified field over a range of documents in the result set of a query.
+ */
++ (instancetype)aggregateFieldForAverageOfField:(NSString *)field NS_SWIFT_NAME(average(_:));
+
+/**
+ * Create an `AggregateField` object that can be used to compute the average of
+ * a specified field over a range of documents in the result set of a query.
+ *
+ * The result of an average operation will always be a 64-bit integer value, a double, or NaN.
+ *
+ * - Averaging over zero documents or fields will result in a double value representing NaN.
+ * - Averaging over NaN will result in a double value representing NaN.
+ *
+ * @param fieldPath Specifies the field to average across the result set.
+ * @return `AggregateField` object that can be used to compute the average of
+ * a specified field over a range of documents in the result set of a query.
+ */
++ (instancetype)aggregateFieldForAverageOfFieldPath:(FIRFieldPath *)fieldPath
+    NS_SWIFT_NAME(average(_:));
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 130 - 0
Firestore/Source/API/FIRAggregateField.mm

@@ -0,0 +1,130 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRAggregateField.h"
+
+#include <string>
+
+#import "Firestore/Source/API/FIRAggregateField+Internal.h"
+#import "Firestore/Source/API/FIRFieldPath+Internal.h"
+
+using firebase::firestore::model::AggregateField;
+
+NS_ASSUME_NONNULL_BEGIN
+
+#pragma mark - FIRAggregateField
+
+@interface FIRAggregateField ()
+@property(nonatomic, strong) FIRFieldPath *_fieldPath;
+@property(nonatomic, readwrite) model::AggregateField::OpKind _op;
+- (instancetype)initWithFieldPathAndKind:(nullable FIRFieldPath *)fieldPath
+                                  opKind:(model::AggregateField::OpKind)op;
+@end
+
+@implementation FIRAggregateField
+- (instancetype)initWithFieldPathAndKind:(nullable FIRFieldPath *)fieldPath
+                                  opKind:(model::AggregateField::OpKind)op {
+  if (self = [super init]) {
+    self._fieldPath = fieldPath;
+    self._op = op;
+  }
+  return self;
+}
+
+- (FIRFieldPath *)fieldPath {
+  return [self _fieldPath];
+}
+
+- (const std::string)name {
+  switch ([self _op]) {
+    case AggregateField::OpKind::Sum:
+      return std::string("sum");
+    case AggregateField::OpKind::Avg:
+      return std::string("avg");
+    case AggregateField::OpKind::Count:
+      return std::string("count");
+  }
+  UNREACHABLE();
+}
+
+- (model::AggregateField)createInternalValue {
+  if (self.fieldPath != Nil) {
+    return model::AggregateField([self _op], [self createAlias], self.fieldPath.internalValue);
+  } else {
+    return model::AggregateField([self _op], [self createAlias]);
+  }
+}
+
+- (model::AggregateAlias)createAlias {
+  if (self.fieldPath != Nil) {
+    return model::AggregateAlias([self name] + std::string{"_"} +
+                                 self.fieldPath.internalValue.CanonicalString());
+  } else {
+    return model::AggregateAlias([self name]);
+  }
+}
+
++ (instancetype)aggregateFieldForCount {
+  return [[FSTCountAggregateField alloc] initPrivate];
+}
+
++ (instancetype)aggregateFieldForSumOfField:(NSString *)field {
+  return [self aggregateFieldForSumOfFieldPath:[FIRFieldPath pathWithDotSeparatedString:field]];
+}
+
++ (instancetype)aggregateFieldForSumOfFieldPath:(FIRFieldPath *)fieldPath {
+  return [[FSTSumAggregateField alloc] initWithFieldPath:fieldPath];
+}
+
++ (instancetype)aggregateFieldForAverageOfField:(NSString *)field {
+  return [self aggregateFieldForAverageOfFieldPath:[FIRFieldPath pathWithDotSeparatedString:field]];
+}
+
++ (instancetype)aggregateFieldForAverageOfFieldPath:(FIRFieldPath *)fieldPath {
+  return [[FSTAverageAggregateField alloc] initWithFieldPath:fieldPath];
+}
+
+@end
+
+#pragma mark - FSTSumAggregateField
+@implementation FSTSumAggregateField
+- (instancetype)initWithFieldPath:(FIRFieldPath *)fieldPath {
+  self = [super initWithFieldPathAndKind:fieldPath opKind:model::AggregateField::OpKind::Sum];
+  return self;
+}
+@end
+
+#pragma mark - FSTAverageAggregateField
+
+@implementation FSTAverageAggregateField
+- (instancetype)initWithFieldPath:(FIRFieldPath *)fieldPath {
+  self = [super initWithFieldPathAndKind:fieldPath opKind:model::AggregateField::OpKind::Avg];
+  return self;
+}
+
+@end
+
+#pragma mark - FSTCountAggregateField
+
+@implementation FSTCountAggregateField
+- (instancetype)initPrivate {
+  self = [super initWithFieldPathAndKind:Nil opKind:model::AggregateField::OpKind::Count];
+  return self;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 5 - 1
Firestore/Source/API/FIRAggregateQuery+Internal.h

@@ -15,6 +15,8 @@
  */
 
 #import "FIRAggregateQuery.h"
+
+#import "FIRAggregateField.h"
 #import "FIRQuery.h"
 
 NS_ASSUME_NONNULL_BEGIN
@@ -22,7 +24,9 @@ NS_ASSUME_NONNULL_BEGIN
 @interface FIRAggregateQuery (/* init */)
 
 - (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithQuery:(FIRQuery *)query NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithQueryAndAggregations:(FIRQuery *)query
+                                aggregations:(NSArray<FIRAggregateField *> *)aggregations
+    NS_DESIGNATED_INITIALIZER;
 
 @end
 

+ 23 - 11
Firestore/Source/API/FIRAggregateQuery.mm

@@ -16,13 +16,17 @@
 
 #import "FIRAggregateQuery+Internal.h"
 
-#import "FIRAggregateQuerySnapshot+Internal.h"
-#import "FIRQuery+Internal.h"
+#import "Firestore/Source/API/FIRAggregateField+Internal.h"
+#import "Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h"
+#import "Firestore/Source/API/FIRQuery+Internal.h"
 
 #include "Firestore/core/src/api/aggregate_query.h"
-#include "Firestore/core/src/api/query_core.h"
 #include "Firestore/core/src/util/error_apple.h"
-#include "Firestore/core/src/util/statusor.h"
+
+using firebase::firestore::api::AggregateQuery;
+using firebase::firestore::model::AggregateField;
+using firebase::firestore::model::ObjectValue;
+using firebase::firestore::util::StatusOr;
 
 NS_ASSUME_NONNULL_BEGIN
 
@@ -30,13 +34,21 @@ NS_ASSUME_NONNULL_BEGIN
 
 @implementation FIRAggregateQuery {
   FIRQuery *_query;
-  std::unique_ptr<api::AggregateQuery> _aggregation;
+  std::unique_ptr<AggregateQuery> _aggregateQuery;
 }
 
-- (instancetype)initWithQuery:(FIRQuery *)query {
+- (instancetype)initWithQueryAndAggregations:(FIRQuery *)query
+                                aggregations:(NSArray<FIRAggregateField *> *)aggregations {
   if (self = [super init]) {
     _query = query;
-    _aggregation = absl::make_unique<api::AggregateQuery>(query.apiQuery.Count());
+
+    std::vector<AggregateField> _aggregateFields;
+    for (FIRAggregateField *field in aggregations) {
+      _aggregateFields.push_back([field createInternalValue]);
+    }
+
+    _aggregateQuery =
+        absl::make_unique<AggregateQuery>(query.apiQuery.Aggregate(std::move(_aggregateFields)));
   }
   return self;
 }
@@ -48,11 +60,11 @@ NS_ASSUME_NONNULL_BEGIN
   if (![[other class] isEqual:[self class]]) return NO;
 
   auto otherQuery = static_cast<FIRAggregateQuery *>(other);
-  return [_query isEqual:otherQuery->_query];
+  return [_query isEqual:otherQuery->_query] && *_aggregateQuery == *(otherQuery->_aggregateQuery);
 }
 
 - (NSUInteger)hash {
-  return [_query hash];
+  return _aggregateQuery->Hash();
 }
 
 #pragma mark - Public Methods
@@ -64,9 +76,9 @@ NS_ASSUME_NONNULL_BEGIN
 - (void)aggregationWithSource:(FIRAggregateSource)source
                    completion:(void (^)(FIRAggregateQuerySnapshot *_Nullable snapshot,
                                         NSError *_Nullable error))completion {
-  _aggregation->Get([self, completion](const firebase::firestore::util::StatusOr<int64_t> &result) {
+  _aggregateQuery->GetAggregate([self, completion](const StatusOr<ObjectValue> &result) {
     if (result.ok()) {
-      completion([[FIRAggregateQuerySnapshot alloc] initWithCount:result.ValueOrDie() query:self],
+      completion([[FIRAggregateQuerySnapshot alloc] initWithObject:result.ValueOrDie() query:self],
                  nil);
     } else {
       completion(nil, MakeNSError(result.status()));

+ 31 - 2
Firestore/Source/API/FIRAggregateQuerySnapshot+Internal.h

@@ -16,15 +16,44 @@
 
 #import "FIRAggregateQuerySnapshot.h"
 
+#import "FIRAggregateField.h"
+#import "FIRDocumentSnapshot.h"
+
+#include "Firestore/core/src/api/api_fwd.h"
+
 @class FIRAggregateQuery;
 
+namespace model = firebase::firestore::model;
+
 NS_ASSUME_NONNULL_BEGIN
 
 @interface FIRAggregateQuerySnapshot (/* init */)
 
 - (instancetype)init NS_UNAVAILABLE;
-- (instancetype)initWithCount:(int64_t)result
-                        query:(FIRAggregateQuery *)query NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithObject:(model::ObjectValue)result
+                         query:(FIRAggregateQuery *)query NS_DESIGNATED_INITIALIZER;
+
+@end
+
+// TODO(sum/avg) move the contents of this FuturePublicApi category to
+// ../Public/FirebaseFirestore/FIRAggregateQuerySnapshot.h
+@interface FIRAggregateQuerySnapshot (FuturePublicApi)
+
+/**
+ * Gets the aggregation result for the specified aggregation without loss of precision. No coercion
+ * of data types or values is performed.
+ *
+ * See the `AggregateField` class for the expected aggregration result values and types. Numeric
+ * aggregation results will be boxed in an `NSNumber`.
+ *
+ * @param aggregation An instance of `AggregateField` that specifies which aggregation result to
+ * return.
+ * @return Returns the aggregation result from the server without loss of precision.
+ * @warning Throws an `InvalidArgument` exception if the aggregation was not requested in the
+ * `AggregateQuery`.
+ * @see `AggregateField`
+ */
+- (nullable id)valueForAggregation:(FIRAggregateField *)aggregation NS_SWIFT_NAME(get(_:));
 
 @end
 

+ 47 - 9
Firestore/Source/API/FIRAggregateQuerySnapshot.mm

@@ -17,17 +17,36 @@
 #import "FIRAggregateQuerySnapshot+Internal.h"
 
 #import "FIRAggregateQuery.h"
+#import "FIRQuery.h"
+
+#import "Firestore/Source/API/FIRAggregateField+Internal.h"
+#import "Firestore/Source/API/FIRFieldPath+Internal.h"
+#import "Firestore/Source/API/FIRFirestore+Internal.h"
+#import "Firestore/Source/API/FSTUserDataWriter.h"
+
+#include "absl/types/optional.h"
+
+#include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
+#include "Firestore/core/src/model/aggregate_alias.h"
+#include "Firestore/core/src/model/field_path.h"
+#include "Firestore/core/src/util/exception.h"
+
+using firebase::firestore::google_firestore_v1_Value;
+using firebase::firestore::model::AggregateAlias;
+using firebase::firestore::model::FieldPath;
+using firebase::firestore::model::ObjectValue;
+using firebase::firestore::util::ThrowInvalidArgument;
 
 NS_ASSUME_NONNULL_BEGIN
 
 @implementation FIRAggregateQuerySnapshot {
-  int64_t _result;
-  FIRAggregateQuery* _query;
+  ObjectValue _result;
+  FIRAggregateQuery *_query;
 }
 
-- (instancetype)initWithCount:(int64_t)count query:(FIRAggregateQuery*)query {
+- (instancetype)initWithObject:(ObjectValue)result query:(FIRAggregateQuery *)query {
   if (self = [super init]) {
-    _result = count;
+    _result = std::move(result);
     _query = query;
   }
   return self;
@@ -39,26 +58,45 @@ NS_ASSUME_NONNULL_BEGIN
   if (other == self) return YES;
   if (![[other class] isEqual:[self class]]) return NO;
 
-  auto otherSnap = static_cast<FIRAggregateQuerySnapshot*>(other);
+  auto otherSnap = static_cast<FIRAggregateQuerySnapshot *>(other);
   return _result == otherSnap->_result && [_query isEqual:otherSnap->_query];
 }
 
 - (NSUInteger)hash {
   NSUInteger result = [_query hash];
-  result = 31 * result + [[self count] hash];
+  result = 31 * result + _result.Hash();
   return result;
 }
 
 #pragma mark - Public Methods
 
-- (NSNumber*)count {
-  return [NSNumber numberWithLongLong:_result];
+- (NSNumber *)count {
+  return (NSNumber *)[self valueForAggregation:[FIRAggregateField aggregateFieldForCount]];
 }
 
-- (FIRAggregateQuery*)query {
+- (FIRAggregateQuery *)query {
   return _query;
 }
 
+- (nullable id)valueForAggregation:(FIRAggregateField *)aggregation {
+  FIRServerTimestampBehavior serverTimestampBehavior = FIRServerTimestampBehaviorNone;
+  AggregateAlias alias = [aggregation createAlias];
+  absl::optional<google_firestore_v1_Value> fieldValue = _result.Get(alias.StringValue());
+  if (!fieldValue) {
+    std::string path{""};
+    if (aggregation.fieldPath) {
+      path = [aggregation.fieldPath internalValue].CanonicalString();
+    }
+
+    ThrowInvalidArgument("'%s(%s)' was not requested in the aggregation query.", [aggregation name],
+                         path);
+  }
+  FSTUserDataWriter *dataWriter =
+      [[FSTUserDataWriter alloc] initWithFirestore:_query.query.firestore.wrapped
+                           serverTimestampBehavior:serverTimestampBehavior];
+  return [dataWriter convertedValue:*fieldValue];
+}
+
 @end
 
 NS_ASSUME_NONNULL_END

+ 24 - 0
Firestore/Source/API/FIRQuery+Internal.h

@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#import "FIRAggregateField.h"
 #import "FIRQuery.h"
 
 #include <memory>
@@ -46,4 +47,27 @@ NS_ASSUME_NONNULL_BEGIN
 
 @end
 
+// TODO(sum/avg) move the contents of this FuturePublicApi category to
+// ../Public/FirebaseFirestore/FIRAggregateQuerySnapshot.h
+@interface FIRQuery (FuturePublicApi)
+
+/**
+ * Creates and returns a new `AggregateQuery` that aggregates the documents in the result set
+ * of this query, without actually downloading the documents.
+ *
+ * Using an `AggregateQuery` to perform aggregations is efficient because only the final aggregation
+ * values, not the documents' data, is downloaded. The query can even aggregate the documents if the
+ * result set would be prohibitively large to download entirely (e.g. thousands of documents).
+ *
+ * @param aggregations Specifies the aggregation operations to perform on the result set of this
+ * query.
+ *
+ * @return An `AggregateQuery` encapsulating this `Query` and `AggregateField`s, which can be used
+ * to query the server for the aggregation results.
+ */
+- (FIRAggregateQuery *)aggregate:(NSArray<FIRAggregateField *> *)aggregations
+    NS_SWIFT_NAME(aggregate(_:));
+
+@end
+
 NS_ASSUME_NONNULL_END

+ 9 - 4
Firestore/Source/API/FIRQuery.mm

@@ -20,9 +20,11 @@
 #include <utility>
 #include <vector>
 
-#import "FIRAggregateQuery+Internal.h"
 #import "FIRDocumentReference.h"
 #import "FIRFirestoreErrors.h"
+
+#import "Firestore/Source/API/FIRAggregateField+Internal.h"
+#import "Firestore/Source/API/FIRAggregateQuery+Internal.h"
 #import "Firestore/Source/API/FIRDocumentReference+Internal.h"
 #import "Firestore/Source/API/FIRDocumentSnapshot+Internal.h"
 #import "Firestore/Source/API/FIRFieldPath+Internal.h"
@@ -58,10 +60,8 @@
 #include "Firestore/core/src/nanopb/nanopb_util.h"
 #include "Firestore/core/src/util/error_apple.h"
 #include "Firestore/core/src/util/exception.h"
-#include "Firestore/core/src/util/hard_assert.h"
 #include "Firestore/core/src/util/statusor.h"
 #include "Firestore/core/src/util/string_apple.h"
-#include "absl/memory/memory.h"
 #include "absl/strings/match.h"
 
 namespace nanopb = firebase::firestore::nanopb;
@@ -484,7 +484,12 @@ int32_t SaturatedLimitValue(NSInteger limit) {
 }
 
 - (FIRAggregateQuery *)count {
-  return [[FIRAggregateQuery alloc] initWithQuery:self];
+  FIRAggregateField *countAF = [FIRAggregateField aggregateFieldForCount];
+  return [[FIRAggregateQuery alloc] initWithQueryAndAggregations:self aggregations:@[ countAF ]];
+}
+
+- (FIRAggregateQuery *)aggregate:(NSArray<FIRAggregateField *> *)aggregations {
+  return [[FIRAggregateQuery alloc] initWithQueryAndAggregations:self aggregations:aggregations];
 }
 
 #pragma mark - Private Methods

+ 401 - 0
Firestore/Swift/Tests/Integration/AggregationIntegrationTests.swift

@@ -0,0 +1,401 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import FirebaseFirestore
+import FirebaseFirestoreSwift
+import Foundation
+
+// TODO(sum/avg) remove `sumAvgIsPublic` from the directive below to enable these tests when sum/avg is public
+#if sumAvgIsPublic && swift(>=5.5.2)
+  @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
+
+  class AggregationIntegrationTests: FSTIntegrationTestCase {
+    func testCount() async throws {
+      let collection = collectionRef()
+      try await collection.addDocument(data: [:])
+      let snapshot = try await collection.count.getAggregation(source: .server)
+      XCTAssertEqual(snapshot.count, 1)
+    }
+
+    func testCanRunAggregateQuery() async throws {
+      // TODO(sum/avg) remove the check below when sum and avg are supported in production
+      try XCTSkipIf(
+        !FSTIntegrationTestCase.isRunningAgainstEmulator(),
+        "only tested against emulator"
+      )
+
+      let collection = collectionRef()
+      try await collection.addDocument(data: ["author": "authorA",
+                                              "title": "titleA",
+                                              "pages": 100,
+                                              "height": 24.5,
+                                              "weight": 24.1,
+                                              "foo": 1,
+                                              "bar": 2,
+                                              "baz": 3])
+      try await collection.addDocument(data: ["author": "authorB",
+                                              "title": "titleB",
+                                              "pages": 50,
+                                              "height": 25.5,
+                                              "weight": 75.5,
+                                              "foo": 1,
+                                              "bar": 2,
+                                              "baz": 3])
+
+      let snapshot = try await collection.aggregate([
+        AggregateField.count(),
+        AggregateField.sum("pages"),
+        AggregateField.sum("weight"),
+        AggregateField.average("pages"),
+        AggregateField.average("weight"),
+      ]).getAggregation(source: .server)
+
+      // Count
+      XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 2)
+
+      // Sum
+      XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 150)
+      XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? Double, 150)
+      XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? Int64, 150)
+      XCTAssertEqual(snapshot.get(AggregateField.sum("weight")) as? NSNumber, 99.6)
+      XCTAssertEqual(snapshot.get(AggregateField.sum("weight")) as? Double, 99.6)
+
+      // Average
+      XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNumber, 75.0)
+      XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? Double, 75.0)
+      XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? Int64, 75)
+      XCTAssertEqual(snapshot.get(AggregateField.average("weight")) as? NSNumber, 49.8)
+      XCTAssertEqual(snapshot.get(AggregateField.average("weight")) as? Double, 49.8)
+    }
+
+    func testCannotPerformMoreThanMaxAggregations() async throws {
+      // TODO(sum/avg) remove the check below when sum and avg are supported in production
+      try XCTSkipIf(
+        !FSTIntegrationTestCase.isRunningAgainstEmulator(),
+        "only tested against emulator"
+      )
+
+      let collection = collectionRef()
+      try await collection.addDocument(data: ["author": "authorA",
+                                              "title": "titleA",
+                                              "pages": 100,
+                                              "height": 24.5,
+                                              "weight": 24.1,
+                                              "foo": 1,
+                                              "bar": 2,
+                                              "baz": 3])
+
+      // Max is 5, we're attempting 6. I also like to live dangerously.
+      do {
+        let snapshot = try await collection.aggregate([
+          AggregateField.count(),
+          AggregateField.sum("pages"),
+          AggregateField.sum("weight"),
+          AggregateField.average("pages"),
+          AggregateField.average("weight"),
+          AggregateField.average("foo"),
+        ]).getAggregation(source: .server)
+        XCTFail("Error expected.")
+      } catch let error as NSError {
+        XCTAssertNotNil(error)
+        XCTAssertTrue(error.localizedDescription.contains("maximum number of aggregations"))
+      }
+    }
+
+    func testPerformsAggregationsWhenNaNExistsForSomeFieldValues() async throws {
+      // TODO(sum/avg) remove the check below when sum and avg are supported in production
+      try XCTSkipIf(
+        !FSTIntegrationTestCase.isRunningAgainstEmulator(),
+        "only tested against emulator"
+      )
+
+      let collection = collectionRef()
+      try await collection.addDocument(data: ["author": "authorA",
+                                              "title": "titleA",
+                                              "pages": 100,
+                                              "year": 1980,
+                                              "rating": 4])
+      try await collection.addDocument(data: ["author": "authorB",
+                                              "title": "titleB",
+                                              "pages": 50,
+                                              "year": 2020,
+                                              "rating": Double.nan])
+
+      let snapshot = try await collection.aggregate([
+        AggregateField.sum("pages"),
+        AggregateField.sum("rating"),
+        AggregateField.average("pages"),
+        AggregateField.average("rating"),
+      ]).getAggregation(source: .server)
+
+      // Sum
+      XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 150)
+      XCTAssertTrue((snapshot.get(AggregateField.sum("rating")) as? Double)?.isNaN ?? false)
+
+      // Average
+      XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNumber, 75.0)
+      XCTAssertTrue((snapshot.get(AggregateField.average("rating")) as? Double)?.isNaN ?? false)
+    }
+
+    func testThrowsAnErrorWhenGettingTheResultOfAnUnrequestedAggregation() async throws {
+      // TODO(sum/avg) remove the check below when sum and avg are supported in production
+      try XCTSkipIf(
+        !FSTIntegrationTestCase.isRunningAgainstEmulator(),
+        "only tested against emulator"
+      )
+
+      let collection = collectionRef()
+      try await collection.addDocument(data: [:])
+
+      let snapshot = try await collection.aggregate([AggregateField.average("foo")])
+        .getAggregation(source: .server)
+
+      XCTAssertTrue(FSTNSExceptionUtil.testForException({
+        snapshot.count
+      }, reasonContains: "'count()' was not requested in the aggregation query"))
+
+      XCTAssertTrue(FSTNSExceptionUtil.testForException({
+        snapshot.get(AggregateField.sum("foo"))
+      }, reasonContains: "'sum(foo)' was not requested in the aggregation query"))
+
+      XCTAssertTrue(FSTNSExceptionUtil.testForException({
+        snapshot.get(AggregateField.average("bar"))
+      }, reasonContains: "'avg(bar)' was not requested in the aggregation query"))
+    }
+
+    func testPerformsAggregationsOnNestedMapValues() async throws {
+      // TODO(sum/avg) remove the check below when sum and avg are supported in production
+      try XCTSkipIf(
+        !FSTIntegrationTestCase.isRunningAgainstEmulator(),
+        "only tested against emulator"
+      )
+
+      let collection = collectionRef()
+      try await collection.addDocument(data: ["metadata": [
+        "pages": 100,
+        "rating": [
+          "critic": 2,
+          "user": 5,
+        ],
+      ]])
+      try await collection.addDocument(data: ["metadata": [
+        "pages": 50,
+        "rating": [
+          "critic": 4,
+          "user": 4,
+        ],
+      ]])
+
+      let snapshot = try await collection.aggregate([
+        AggregateField.count(),
+        AggregateField.sum("metadata.pages"),
+        AggregateField.sum(FieldPath(["metadata", "rating", "user"])),
+        AggregateField.average("metadata.pages"),
+        AggregateField.average(FieldPath(["metadata", "rating", "critic"])),
+      ]).getAggregation(source: .server)
+
+      // Count
+      XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 2)
+
+      // Sum
+      XCTAssertEqual(
+        snapshot.get(AggregateField.sum(FieldPath(["metadata", "pages"]))) as? NSNumber,
+        150
+      )
+      XCTAssertEqual(snapshot.get(AggregateField.sum("metadata.pages")) as? NSNumber, 150)
+      XCTAssertEqual(snapshot.get(AggregateField.sum("metadata.rating.user")) as? NSNumber, 9)
+
+      // Average
+      XCTAssertEqual(
+        snapshot.get(AggregateField.average(FieldPath(["metadata", "pages"]))) as? Double,
+        75.0
+      )
+      XCTAssertEqual(
+        snapshot.get(AggregateField.average("metadata.rating.critic")) as? Double,
+        3.0
+      )
+    }
+
+    func testSumOverflow() async throws {
+      // TODO(sum/avg) remove the check below when sum and avg are supported in production
+      try XCTSkipIf(
+        !FSTIntegrationTestCase.isRunningAgainstEmulator(),
+        "only tested against emulator"
+      )
+
+      let collection = collectionRef()
+      try await collection.addDocument(data: [
+        "longOverflow": Int64.max,
+        "accumulationOverflow": Int64.max,
+        "positiveInfinity": Double.greatestFiniteMagnitude,
+        "negativeInfinity": -Double.greatestFiniteMagnitude,
+      ])
+      try await collection.addDocument(data: [
+        "longOverflow": Int64.max,
+        "accumulationOverflow": 1,
+        "positiveInfinity": Double.greatestFiniteMagnitude,
+        "negativeInfinity": -Double.greatestFiniteMagnitude,
+      ])
+      try await collection.addDocument(data: [
+        "longOverflow": Int64.max,
+        "accumulationOverflow": -101,
+        "positiveInfinity": Double.greatestFiniteMagnitude,
+        "negativeInfinity": -Double.greatestFiniteMagnitude,
+      ])
+
+      let snapshot = try await collection.aggregate([
+        AggregateField.sum("longOverflow"),
+        AggregateField.sum("accumulationOverflow"),
+        AggregateField.sum("positiveInfinity"),
+        AggregateField.sum("negativeInfinity"),
+      ]).getAggregation(source: .server)
+
+      // Sum
+      XCTAssertEqual(
+        snapshot.get(AggregateField.sum("longOverflow")) as? Double,
+        Double(Int64.max) + Double(Int64.max) + Double(Int64.max)
+      )
+      XCTAssertEqual(
+        snapshot.get(AggregateField.sum("accumulationOverflow")) as? Int64,
+        Int64.max - 100
+      )
+      XCTAssertEqual(
+        snapshot.get(AggregateField.sum("positiveInfinity")) as? Double,
+        Double.infinity
+      )
+      XCTAssertEqual(
+        snapshot.get(AggregateField.sum("negativeInfinity")) as? Double,
+        -Double.infinity
+      )
+    }
+
+    func testAverageOverflow() async throws {
+      // TODO(sum/avg) remove the check below when sum and avg are supported in production
+      try XCTSkipIf(
+        !FSTIntegrationTestCase.isRunningAgainstEmulator(),
+        "only tested against emulator"
+      )
+
+      let collection = collectionRef()
+      try await collection.addDocument(data: [
+        "longOverflow": Int64.max,
+        "doubleOverflow": Double.greatestFiniteMagnitude,
+        "negativeInfinity": -Double.greatestFiniteMagnitude,
+      ])
+      try await collection.addDocument(data: [
+        "longOverflow": Int64.max,
+        "doubleOverflow": Double.greatestFiniteMagnitude,
+        "negativeInfinity": -Double.greatestFiniteMagnitude,
+      ])
+      try await collection.addDocument(data: [
+        "longOverflow": Int64.max,
+        "doubleOverflow": Double.greatestFiniteMagnitude,
+        "negativeInfinity": -Double.greatestFiniteMagnitude,
+      ])
+
+      let snapshot = try await collection.aggregate([
+        AggregateField.average("longOverflow"),
+        AggregateField.average("doubleOverflow"),
+        AggregateField.average("negativeInfinity"),
+      ]).getAggregation(source: .server)
+
+      // Average
+      XCTAssertEqual(
+        snapshot.get(AggregateField.average("longOverflow")) as? Double,
+        Double(Int64.max)
+      )
+      XCTAssertEqual(
+        snapshot.get(AggregateField.average("doubleOverflow")) as? Double,
+        Double.infinity
+      )
+      XCTAssertEqual(
+        snapshot.get(AggregateField.average("negativeInfinity")) as? Double,
+        -Double.infinity
+      )
+    }
+
+    func testAverageUnderflow() async throws {
+      // TODO(sum/avg) remove the check below when sum and avg are supported in production
+      try XCTSkipIf(
+        !FSTIntegrationTestCase.isRunningAgainstEmulator(),
+        "only tested against emulator"
+      )
+
+      let collection = collectionRef()
+      try await collection.addDocument(data: ["underflowSmall": Double.leastNonzeroMagnitude])
+      try await collection.addDocument(data: ["underflowSmall": 0])
+
+      let snapshot = try await collection.aggregate([AggregateField.average("underflowSmall")])
+        .getAggregation(source: .server)
+
+      // Average
+      XCTAssertEqual(snapshot.get(AggregateField.average("underflowSmall")) as? Double, 0.0)
+    }
+
+    func testPerformsAggregateOverResultSetOfZeroDocuments() async throws {
+      // TODO(sum/avg) remove the check below when sum and avg are supported in production
+      try XCTSkipIf(
+        !FSTIntegrationTestCase.isRunningAgainstEmulator(),
+        "only tested against emulator"
+      )
+
+      let collection = collectionRef()
+      try await collection.addDocument(data: ["pages": 100])
+      try await collection.addDocument(data: ["pages": 50])
+
+      let snapshot = try await collection.whereField("pages", isGreaterThan: 200)
+        .aggregate([AggregateField.count(), AggregateField.sum("pages"),
+                    AggregateField.average("pages")]).getAggregation(source: .server)
+
+      // Count
+      XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 0)
+
+      // Sum
+      XCTAssertEqual(snapshot.get(AggregateField.sum("pages")) as? NSNumber, 0)
+
+      // Average
+      // TODO: (sum/avg) this design is bad, will require and API update
+      XCTAssertEqual(snapshot.get(AggregateField.average("pages")) as? NSNull, NSNull())
+    }
+
+    func testPerformsAggregateOverResultSetOfZeroFields() async throws {
+      // TODO(sum/avg) remove the check below when sum and avg are supported in production
+      try XCTSkipIf(
+        !FSTIntegrationTestCase.isRunningAgainstEmulator(),
+        "only tested against emulator"
+      )
+
+      let collection = collectionRef()
+      try await collection.addDocument(data: ["pages": 100])
+      try await collection.addDocument(data: ["pages": 50])
+
+      let snapshot = try await collection
+        .aggregate([AggregateField.count(), AggregateField.sum("notInMyDocs"),
+                    AggregateField.average("notInMyDocs")]).getAggregation(source: .server)
+
+      // Count  - 0 because aggregation is performed on documents matching the query AND documents that have all aggregated fields
+      XCTAssertEqual(snapshot.get(AggregateField.count()) as? NSNumber, 0)
+
+      // Sum
+      XCTAssertEqual(snapshot.get(AggregateField.sum("notInMyDocs")) as? NSNumber, 0)
+
+      // Average
+      // TODO: (sum/avg) this design is bad, will require and API update
+      XCTAssertEqual(snapshot.get(AggregateField.average("notInMyDocs")) as? NSNull, NSNull())
+    }
+  }
+
+#endif

+ 0 - 7
Firestore/Swift/Tests/Integration/AsyncAwaitIntegrationTests.swift

@@ -74,13 +74,6 @@ let emptyBundle = """
       XCTAssertNil(value, "value should be nil on success")
     }
 
-    func testCount() async throws {
-      let collection = collectionRef()
-      try await collection.addDocument(data: [:])
-      let snapshot = try await collection.count.getAggregation(source: .server)
-      XCTAssertEqual(snapshot.count, 1)
-    }
-
     func testQuery() async throws {
       let collRef = collectionRef(
         withDocuments: ["doc1": ["a": 1, "b": 0],

+ 35 - 6
Firestore/core/src/api/aggregate_query.cc

@@ -21,21 +21,50 @@
 #include "Firestore/core/src/api/api_fwd.h"
 #include "Firestore/core/src/api/firestore.h"
 #include "Firestore/core/src/core/firestore_client.h"
+#include "Firestore/core/src/model/aggregate_field.h"
+
+using firebase::firestore::model::AggregateAlias;
+using firebase::firestore::model::AggregateField;
 
 namespace firebase {
 namespace firestore {
 namespace api {
 
-AggregateQuery::AggregateQuery(Query query) : query_{std::move(query)} {
+bool operator==(const AggregateQuery& lhs, const AggregateQuery& rhs) {
+  return lhs.query_ == rhs.query_ && lhs.aggregates_ == rhs.aggregates_;
 }
 
-void AggregateQuery::Get(CountQueryCallback&& callback) {
-  query_.firestore()->client()->RunCountQuery(query_.query(),
-                                              std::move(callback));
+size_t AggregateQuery::Hash() const {
+  return util::Hash(query_, aggregates_);
 }
 
-bool operator==(const AggregateQuery& lhs, const AggregateQuery& rhs) {
-  return lhs.query() == rhs.query();
+AggregateQuery::AggregateQuery(Query query,
+                               std::vector<AggregateField>&& aggregates)
+    : query_{std::move(query)}, aggregates_{std::move(aggregates)} {
+}
+
+void AggregateQuery::GetAggregate(AggregateQueryCallback&& callback) {
+  query_.firestore()->client()->RunAggregateQuery(query_.query(), aggregates_,
+                                                  std::move(callback));
+}
+
+// TODO(b/280805906) Remove this count specific API after the c++ SDK migrates
+// to the new Aggregate API
+void AggregateQuery::Get(CountQueryCallback&& callback) {
+  this->GetAggregate(
+      [callback = std::move(callback)](const StatusOr<ObjectValue>& result) {
+        if (!result.ok()) {
+          callback(StatusOr<int64_t>(std::move(result.status())));
+          return;
+        }
+
+        absl::optional<google_firestore_v1_Value> count_value =
+            result.ValueOrDie().Get(AggregateAlias("count").StringValue());
+        HARD_ASSERT(count_value.has_value() &&
+                    count_value.value().which_value_type ==
+                        google_firestore_v1_Value_integer_value_tag);
+        callback(StatusOr<int64_t>(count_value->integer_value));
+      });
 }
 
 }  // namespace api

+ 23 - 1
Firestore/core/src/api/aggregate_query.h

@@ -16,8 +16,12 @@
 #ifndef FIRESTORE_CORE_SRC_API_AGGREGATE_QUERY_H_
 #define FIRESTORE_CORE_SRC_API_AGGREGATE_QUERY_H_
 
+#include <vector>
+
 #include "Firestore/core/src/api/query_core.h"
 
+using firebase::firestore::model::AggregateField;
+
 namespace firebase {
 namespace firestore {
 namespace api {
@@ -29,16 +33,34 @@ namespace api {
  */
 class AggregateQuery {
  public:
-  explicit AggregateQuery(Query query);
+  explicit AggregateQuery(Query query,
+                          std::vector<AggregateField>&& aggregates);
+
+  // TODO(b/280805906) this destructor is marked as virtual because this class
+  // is mocked in api/aggregate_query_test.cc. The virtual keyword can be
+  // removed when the tests and mocking are removed.
+  virtual ~AggregateQuery() = default;
 
   const Query& query() const {
     return query_;
   }
 
+  friend bool operator==(const AggregateQuery& lhs, const AggregateQuery& rhs);
+  size_t Hash() const;
+
+  // TODO(b/280805906) this method is marked as virtual to allow mocking
+  // in api/aggregate_query_test.cc. The virtual keyword can be removed
+  // when the tests and mocking are removed.
+  virtual void GetAggregate(AggregateQueryCallback&& callback);
+
+  // TODO(b/280805906) Remove this count specific API after the c++ SDK migrates
+  // to the new Aggregate API Backward-compatible getter for count result
   void Get(CountQueryCallback&& callback);
 
  private:
+  friend class AggregateQueryTest;
   Query query_;
+  std::vector<AggregateField> aggregates_;
 };
 
 bool operator==(const AggregateQuery& lhs, const AggregateQuery& rhs);

+ 9 - 0
Firestore/core/src/api/api_fwd.h

@@ -20,9 +20,13 @@
 #include <functional>
 #include <memory>
 
+#include "Firestore/core/src/model/object_value.h"
 #include "Firestore/core/src/util/statusor.h"
 #include "absl/types/optional.h"
 
+using firebase::firestore::model::ObjectValue;
+using firebase::firestore::util::StatusOr;
+
 namespace firebase {
 namespace firestore {
 
@@ -55,6 +59,11 @@ using QuerySnapshotListener =
     std::unique_ptr<core::EventListener<QuerySnapshot>>;
 
 using QueryCallback = std::function<void(core::Query, bool)>;
+using AggregateQueryCallback =
+    std::function<void(const StatusOr<ObjectValue>&)>;
+
+// TODO(b/280805906) Remove this count specific API after the c++ SDK migrates
+// to the new Aggregate API
 using CountQueryCallback = std::function<void(const util::StatusOr<int64_t>&)>;
 
 }  // namespace api

+ 13 - 1
Firestore/core/src/api/query_core.cc

@@ -33,6 +33,7 @@
 #include "Firestore/core/src/core/firestore_client.h"
 #include "Firestore/core/src/core/listen_options.h"
 #include "Firestore/core/src/core/operator.h"
+#include "Firestore/core/src/model/aggregate_field.h"
 #include "Firestore/core/src/model/resource_path.h"
 #include "Firestore/core/src/model/value_util.h"
 #include "Firestore/core/src/nanopb/nanopb_util.h"
@@ -57,6 +58,8 @@ using core::IsDisjunctiveOperator;
 using core::ListenOptions;
 using core::QueryListener;
 using core::ViewSnapshot;
+using model::AggregateAlias;
+using model::AggregateField;
 using model::DocumentKey;
 using model::FieldPath;
 using model::GetTypeOrder;
@@ -473,8 +476,17 @@ std::string Query::Describe(Operator op) const {
   UNREACHABLE();
 }
 
+AggregateQuery Query::Aggregate(
+    std::vector<AggregateField>&& aggregations) const {
+  return AggregateQuery(*this, std::move(aggregations));
+}
+
+// TODO(b/280805906) Remove this count specific API after the c++ SDK migrates
+// to the new Aggregate API
 AggregateQuery Query::Count() const {
-  return AggregateQuery(*this);
+  return AggregateQuery(
+      *this, std::vector<AggregateField>{AggregateField(
+                 AggregateField::OpKind::Count, AggregateAlias("count"))});
 }
 
 }  // namespace api

+ 20 - 1
Firestore/core/src/api/query_core.h

@@ -20,12 +20,16 @@
 #include <memory>
 #include <string>
 #include <utility>
+#include <vector>
 
 #include "Firestore/core/src/api/api_fwd.h"
 #include "Firestore/core/src/core/core_fwd.h"
 #include "Firestore/core/src/core/query.h"
+#include "Firestore/core/src/model/aggregate_field.h"
 #include "Firestore/core/src/nanopb/message.h"
 
+using firebase::firestore::model::AggregateField;
+
 namespace firebase {
 namespace firestore {
 
@@ -186,9 +190,24 @@ class Query {
     return Query(std::move(chained_query), firestore_);
   }
 
+  /**
+   * Creates a new `AggregateQuery` that performs the specified aggregations.
+   *
+   * @param aggregations The aggregations to be performed by the created
+   * `AggregateQuery`.
+   *
+   * @return The created `AggregateQuery`.
+   */
+  AggregateQuery Aggregate(std::vector<AggregateField>&& aggregations) const;
+
+  // TODO(b/280805906) Remove this count specific API after the c++ SDK migrates
+  // to the new Aggregate API
   /**
    * Creates a new `AggregateQuery` counting the number of documents matching
-   * this query.
+   * this query. This API is preserved for backward-compatability with
+   * the c++ SDK.
+   *
+   * @return The created `AggregateQuery`.
    */
   AggregateQuery Count() const;
 

+ 11 - 5
Firestore/core/src/core/firestore_client.cc

@@ -44,6 +44,7 @@
 #include "Firestore/core/src/local/proto_sizer.h"
 #include "Firestore/core/src/local/query_engine.h"
 #include "Firestore/core/src/local/query_result.h"
+#include "Firestore/core/src/model/aggregate_field.h"
 #include "Firestore/core/src/model/database_id.h"
 #include "Firestore/core/src/model/document.h"
 #include "Firestore/core/src/model/document_set.h"
@@ -84,11 +85,13 @@ using local::LruParams;
 using local::MemoryPersistence;
 using local::QueryEngine;
 using local::QueryResult;
+using model::AggregateField;
 using model::Document;
 using model::DocumentKeySet;
 using model::DocumentMap;
 using model::FieldIndex;
 using model::Mutation;
+using model::ObjectValue;
 using model::OnlineState;
 using remote::ConnectivityMonitor;
 using remote::Datastore;
@@ -552,20 +555,23 @@ void FirestoreClient::Transaction(int max_attempts,
   });
 }
 
-void FirestoreClient::RunCountQuery(const Query& query,
-                                    api::CountQueryCallback&& result_callback) {
+void FirestoreClient::RunAggregateQuery(
+    const Query& query,
+    const std::vector<AggregateField>& aggregates,
+    api::AggregateQueryCallback&& result_callback) {
   VerifyNotTerminated();
 
   // Dispatch the result back onto the user dispatch queue.
   auto async_callback = [this,
-                         result_callback](const StatusOr<int64_t>& status) {
+                         result_callback](const StatusOr<ObjectValue>& status) {
     if (result_callback) {
       user_executor_->Execute([=] { result_callback(std::move(status)); });
     }
   };
 
-  worker_queue_->Enqueue([this, query, async_callback] {
-    sync_engine_->RunCountQuery(query, std::move(async_callback));
+  worker_queue_->Enqueue([this, query, aggregates, async_callback] {
+    sync_engine_->RunAggregateQuery(query, aggregates,
+                                    std::move(async_callback));
   });
 }
 

+ 4 - 2
Firestore/core/src/core/firestore_client.h

@@ -49,6 +49,7 @@ class QueryEngine;
 namespace model {
 class Mutation;
 class FieldIndex;
+class AggregateField;
 }  // namespace model
 
 namespace remote {
@@ -154,8 +155,9 @@ class FirestoreClient : public std::enable_shared_from_this<FirestoreClient> {
   /**
    * Executes a count query using the given query as the base.
    */
-  void RunCountQuery(const Query& query,
-                     api::CountQueryCallback&& result_callback);
+  void RunAggregateQuery(const Query& query,
+                         const std::vector<model::AggregateField>& aggregates,
+                         api::AggregateQueryCallback&& result_callback);
 
   /**
    * Adds a listener to be called when a snapshots-in-sync event fires.

+ 8 - 3
Firestore/core/src/core/sync_engine.cc

@@ -28,6 +28,7 @@
 #include "Firestore/core/src/local/local_write_result.h"
 #include "Firestore/core/src/local/query_result.h"
 #include "Firestore/core/src/local/target_data.h"
+#include "Firestore/core/src/model/aggregate_field.h"
 #include "Firestore/core/src/model/document_key.h"
 #include "Firestore/core/src/model/document_key_set.h"
 #include "Firestore/core/src/model/document_set.h"
@@ -56,6 +57,7 @@ using local::LocalWriteResult;
 using local::QueryPurpose;
 using local::QueryResult;
 using local::TargetData;
+using model::AggregateField;
 using model::BatchId;
 using model::DocumentKey;
 using model::DocumentKeySet;
@@ -250,9 +252,12 @@ void SyncEngine::Transaction(int max_attempts,
   runner->Run();
 }
 
-void SyncEngine::RunCountQuery(const core::Query& query,
-                               api::CountQueryCallback&& result_callback) {
-  remote_store_->RunCountQuery(query, std::move(result_callback));
+void SyncEngine::RunAggregateQuery(
+    const core::Query& query,
+    const std::vector<model::AggregateField>& aggregates,
+    api::AggregateQueryCallback&& result_callback) {
+  remote_store_->RunAggregateQuery(query, aggregates,
+                                   std::move(result_callback));
 }
 
 void SyncEngine::HandleCredentialChange(const credentials::User& user) {

+ 8 - 3
Firestore/core/src/core/sync_engine.h

@@ -47,6 +47,10 @@ class LocalStore;
 class TargetData;
 }  // namespace local
 
+namespace model {
+class AggregateField;
+}  // namespace model
+
 namespace core {
 
 class SyncEngineCallback;
@@ -139,10 +143,11 @@ class SyncEngine : public remote::RemoteStoreCallback, public QueryEventSource {
                    core::TransactionResultCallback result_callback);
 
   /**
-   * Executes a count query using the given query as the base.
+   * Executes an aggregation query.
    */
-  void RunCountQuery(const core::Query& query,
-                     api::CountQueryCallback&& result_callback);
+  void RunAggregateQuery(const core::Query& query,
+                         const std::vector<model::AggregateField>& aggregates,
+                         api::AggregateQueryCallback&& result_callback);
 
   void HandleCredentialChange(const credentials::User& user);
 

+ 39 - 0
Firestore/core/src/model/aggregate_alias.cc

@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Firestore/core/src/model/aggregate_alias.h"
+
+#include "Firestore/core/src/util/hashing.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+const std::string& AggregateAlias::StringValue() const {
+  return _alias;
+}
+
+bool operator==(const AggregateAlias& lhs, const AggregateAlias& rhs) {
+  return lhs._alias == rhs._alias;
+}
+
+size_t AggregateAlias::Hash() const {
+  return util::Hash(_alias);
+}
+
+}  // namespace model
+}  // namespace firestore
+}  // namespace firebase

+ 47 - 0
Firestore/core/src/model/aggregate_alias.h

@@ -0,0 +1,47 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_MODEL_AGGREGATE_ALIAS_H_
+#define FIRESTORE_CORE_SRC_MODEL_AGGREGATE_ALIAS_H_
+
+#include <string>
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+class AggregateAlias {
+ public:
+  AggregateAlias() : _alias() {
+  }
+  explicit AggregateAlias(const std::string alias) : _alias(alias) {
+  }
+
+  const std::string& StringValue() const;
+
+  friend bool operator==(const AggregateAlias& lhs, const AggregateAlias& rhs);
+
+  size_t Hash() const;
+
+ private:
+  const std::string _alias;
+};
+
+}  // namespace model
+}  // namespace firestore
+}  // namespace firebase
+
+#endif  // FIRESTORE_CORE_SRC_MODEL_AGGREGATE_ALIAS_H_

+ 31 - 0
Firestore/core/src/model/aggregate_field.cc

@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 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 <vector>
+
+#include "Firestore/core/src/model/aggregate_field.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+size_t AggregateField::Hash() const {
+  return util::Hash(op, alias, fieldPath.CanonicalString());
+}
+
+}  // namespace model
+}  // namespace firestore
+}  // namespace firebase

+ 62 - 0
Firestore/core/src/model/aggregate_field.h

@@ -0,0 +1,62 @@
+/*
+ * Copyright 2023 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef FIRESTORE_CORE_SRC_MODEL_AGGREGATE_FIELD_H_
+#define FIRESTORE_CORE_SRC_MODEL_AGGREGATE_FIELD_H_
+
+#include <string>
+#include <utility>
+
+#include "Firestore/core/src/model/aggregate_alias.h"
+#include "Firestore/core/src/model/aggregate_field.h"
+#include "Firestore/core/src/model/field_path.h"
+
+namespace firebase {
+namespace firestore {
+namespace model {
+
+class AggregateField {
+ public:
+  enum class OpKind { Sum, Avg, Count };
+
+  const OpKind op;
+  const model::AggregateAlias alias;
+  const model::FieldPath fieldPath;
+
+  AggregateField(OpKind op, model::AggregateAlias&& alias)
+      : op(op), alias(std::move(alias)) {
+  }
+  AggregateField(OpKind op,
+                 model::AggregateAlias&& alias,
+                 const model::FieldPath& fieldPath)
+      : op(op), alias(std::move(alias)), fieldPath(fieldPath) {
+  }
+
+  friend bool operator==(const AggregateField& lhs, const AggregateField& rhs);
+
+  size_t Hash() const;
+};
+
+inline bool operator==(const AggregateField& lhs, const AggregateField& rhs) {
+  return lhs.op == rhs.op && lhs.alias == rhs.alias &&
+         lhs.fieldPath.CanonicalString() == rhs.fieldPath.CanonicalString();
+}
+
+}  // namespace model
+}  // namespace firestore
+}  // namespace firebase
+
+#endif  // FIRESTORE_CORE_SRC_MODEL_AGGREGATE_FIELD_H_

+ 44 - 0
Firestore/core/src/model/object_value.cc

@@ -21,10 +21,14 @@
 #include <set>
 
 #include "Firestore/Protos/nanopb/google/firestore/v1/document.nanopb.h"
+#include "Firestore/core/src/model/value_util.h"
+#include "Firestore/core/src/nanopb/byte_string.h"
 #include "Firestore/core/src/nanopb/fields_array.h"
 #include "Firestore/core/src/nanopb/message.h"
 #include "Firestore/core/src/nanopb/nanopb_util.h"
 #include "Firestore/core/src/util/hashing.h"
+
+#include "absl/strings/str_format.h"
 #include "absl/types/span.h"
 
 namespace firebase {
@@ -33,6 +37,8 @@ namespace model {
 
 namespace {
 
+using model::DeepClone;
+using nanopb::ByteString;
 using nanopb::CheckedSize;
 using nanopb::FreeFieldsArray;
 using nanopb::FreeNanopbMessage;
@@ -221,6 +227,37 @@ ObjectValue ObjectValue::FromFieldsEntry(
   return ObjectValue{std::move(value)};
 }
 
+ObjectValue ObjectValue::FromAggregateFieldsEntry(
+    google_firestore_v1_AggregationResult_AggregateFieldsEntry* fields_entry,
+    pb_size_t count,
+    const absl::flat_hash_map<std::string, std::string>& aliasMap) {
+  Message<google_firestore_v1_Value> value;
+  value->which_value_type = google_firestore_v1_Value_map_value_tag;
+
+  SetRepeatedField(
+      &value->map_value.fields, &value->map_value.fields_count,
+      absl::Span<google_firestore_v1_AggregationResult_AggregateFieldsEntry>(
+          fields_entry, count),
+      [&aliasMap](
+          const google_firestore_v1_AggregationResult_AggregateFieldsEntry&
+              entry) {
+        // Remap the short-form aliases that were sent to the server
+        // to the client-side aliases. Users will access the results
+        // using the client-side alias.
+        ByteString serverAlias(entry.key);
+        std::string serverAliasString = serverAlias.ToString();
+        HARD_ASSERT(aliasMap.contains(serverAliasString),
+                    "%s not present in aliasMap", serverAlias.ToString());
+
+        ByteString clientAlias(aliasMap.find(serverAliasString)->second);
+
+        return google_firestore_v1_MapValue_FieldsEntry{
+            clientAlias.release(), *DeepClone(entry.value).release()};
+      });
+
+  return ObjectValue{std::move(value)};
+}
+
 FieldMask ObjectValue::ToFieldMask() const {
   return ExtractFieldMask(value_->map_value);
 }
@@ -269,6 +306,13 @@ absl::optional<google_firestore_v1_Value> ObjectValue::Get(
   return nested_value;
 }
 
+absl::optional<google_firestore_v1_Value> ObjectValue::Get(
+    const std::string& key) const {
+  google_firestore_v1_MapValue_FieldsEntry* entry = FindEntry(*value_, key);
+  if (!entry) return absl::nullopt;
+  return entry->value;
+}
+
 google_firestore_v1_Value ObjectValue::Get() const {
   return *value_;
 }

+ 24 - 0
Firestore/core/src/model/object_value.h

@@ -30,6 +30,8 @@
 #include "Firestore/core/src/model/value_util.h"
 #include "Firestore/core/src/nanopb/message.h"
 #include "Firestore/core/src/util/hard_assert.h"
+
+#include "absl/container/flat_hash_map.h"
 #include "absl/types/optional.h"
 
 namespace firebase {
@@ -67,6 +69,20 @@ class ObjectValue {
   static ObjectValue FromFieldsEntry(
       google_firestore_v1_Document_FieldsEntry* fields_entry, pb_size_t count);
 
+  /**
+   * Creates a new ObjectValue that is backed by the provided aggregation
+   * result. ObjectValue takes on ownership of the data and zeroes out the
+   * pointers in `fields_entry`. This allows the callsite to destruct the
+   * AggregationResult proto without affecting the fields data.
+   * @param fields_entry The ObjectValue will be backed by this data.
+   * @param count Count of fields in `fields_entry`.
+   * @return The created `ObjectValue`.
+   */
+  static ObjectValue FromAggregateFieldsEntry(
+      google_firestore_v1_AggregationResult_AggregateFieldsEntry* fields_entry,
+      pb_size_t count,
+      const absl::flat_hash_map<std::string, std::string>& aliasMap);
+
   /** Recursively extracts the FieldPaths that are set in this ObjectValue. */
   FieldMask ToFieldMask() const;
 
@@ -78,6 +94,14 @@ class ObjectValue {
    */
   absl::optional<google_firestore_v1_Value> Get(const FieldPath& path) const;
 
+  /**
+   * Returns the value with the given key or null if it doesn't exist.
+   *
+   * @param key the key to search
+   * @return The value at the key or null if it doesn't exist.
+   */
+  absl::optional<google_firestore_v1_Value> Get(const std::string& key) const;
+
   /**
    * Returns the ObjectValue in its Protobuf representation.
    */

+ 28 - 26
Firestore/core/src/remote/datastore.cc

@@ -23,9 +23,7 @@
 #include "Firestore/core/src/core/database_info.h"
 #include "Firestore/core/src/core/query.h"
 #include "Firestore/core/src/credentials/auth_token.h"
-#include "Firestore/core/src/credentials/credentials_provider.h"
-#include "Firestore/core/src/model/database_id.h"
-#include "Firestore/core/src/model/document.h"
+#include "Firestore/core/src/model/aggregate_field.h"
 #include "Firestore/core/src/model/document_key.h"
 #include "Firestore/core/src/model/mutation.h"
 #include "Firestore/core/src/remote/connectivity_monitor.h"
@@ -33,11 +31,9 @@
 #include "Firestore/core/src/remote/grpc_completion.h"
 #include "Firestore/core/src/remote/grpc_connection.h"
 #include "Firestore/core/src/remote/grpc_nanopb.h"
-#include "Firestore/core/src/remote/grpc_stream.h"
 #include "Firestore/core/src/remote/grpc_streaming_reader.h"
 #include "Firestore/core/src/remote/grpc_unary_call.h"
 #include "Firestore/core/src/util/async_queue.h"
-#include "Firestore/core/src/util/error_apple.h"
 #include "Firestore/core/src/util/executor.h"
 #include "Firestore/core/src/util/hard_assert.h"
 #include "Firestore/core/src/util/log.h"
@@ -54,6 +50,7 @@ namespace {
 using core::DatabaseInfo;
 using credentials::AuthCredentialsProvider;
 using credentials::AuthToken;
+using model::AggregateField;
 using model::DocumentKey;
 using model::Mutation;
 using util::AsyncQueue;
@@ -258,29 +255,35 @@ void Datastore::LookupDocumentsWithCredentials(
   call->Start(keys.size(), responses_callback, close_callback);
 }
 
-void Datastore::RunCountQuery(const core::Query& query,
-                              api::CountQueryCallback&& result_callback) {
+void Datastore::RunAggregateQuery(
+    const core::Query& query,
+    const std::vector<AggregateField>& aggregates,
+    api::AggregateQueryCallback&& result_callback) {
   ResumeRpcWithCredentials(
       // TODO(c++14): move into lambda.
-      [this, query, result_callback](
+      [this, query, aggregates, result_callback](
           const StatusOr<AuthToken>& auth_token,
           const std::string& app_check_token) mutable {
         if (!auth_token.ok()) {
           result_callback(auth_token.status());
           return;
         }
-        RunCountQueryWithCredentials(auth_token.ValueOrDie(), app_check_token,
-                                     query, std::move(result_callback));
+        RunAggregateQueryWithCredentials(auth_token.ValueOrDie(),
+                                         app_check_token, query, aggregates,
+                                         std::move(result_callback));
       });
 }
 
-void Datastore::RunCountQueryWithCredentials(
+void Datastore::RunAggregateQueryWithCredentials(
     const credentials::AuthToken& auth_token,
     const std::string& app_check_token,
     const core::Query& query,
-    api::CountQueryCallback&& callback) {
+    const std::vector<AggregateField>& aggregates,
+    api::AggregateQueryCallback&& callback) {
+  absl::flat_hash_map<std::string, std::string> aliasMap;
   grpc::ByteBuffer message =
-      MakeByteBuffer(datastore_serializer_.EncodeCountQueryRequest(query));
+      MakeByteBuffer(datastore_serializer_.EncodeAggregateQueryRequest(
+          query, aggregates, aliasMap));
 
   std::unique_ptr<GrpcUnaryCall> call_owning =
       grpc_connection_.CreateUnaryCall(kRpcNameRunAggregationQuery, auth_token,
@@ -288,21 +291,20 @@ void Datastore::RunCountQueryWithCredentials(
   GrpcUnaryCall* call = call_owning.get();
   active_calls_.push_back(std::move(call_owning));
 
-  call->Start(
-      // TODO(c++14): move into lambda.
-      [this, call, callback](const StatusOr<grpc::ByteBuffer>& result) {
-        LogGrpcCallFinished("RunAggregationQuery", call, result.status());
-        HandleCallStatus(result.status());
+  call->Start([this, call, callback, aliasMap = std::move(aliasMap)](
+                  const StatusOr<grpc::ByteBuffer>& result) {
+    LogGrpcCallFinished("RunAggregationQuery", call, result.status());
+    HandleCallStatus(result.status());
 
-        if (result.ok()) {
-          callback(datastore_serializer_.DecodeCountQueryResponse(
-              result.ValueOrDie()));
-        } else {
-          callback(result.status());
-        }
+    if (result.ok()) {
+      callback(datastore_serializer_.DecodeAggregateQueryResponse(
+          result.ValueOrDie(), aliasMap));
+    } else {
+      callback(result.status());
+    }
 
-        RemoveGrpcCall(call);
-      });
+    RemoveGrpcCall(call);
+  });
 }
 
 void Datastore::ResumeRpcWithCredentials(const OnCredentials& on_credentials) {

+ 10 - 6
Firestore/core/src/remote/datastore.h

@@ -45,6 +45,7 @@ namespace firestore {
 
 namespace model {
 class Document;
+class AggregateField;
 };  // namespace model
 
 namespace remote {
@@ -107,8 +108,9 @@ class Datastore : public std::enable_shared_from_this<Datastore> {
   void LookupDocuments(const std::vector<model::DocumentKey>& keys,
                        LookupCallback&& user_callback);
 
-  void RunCountQuery(const core::Query& query,
-                     api::CountQueryCallback&& result_callback);
+  void RunAggregateQuery(const core::Query& query,
+                         const std::vector<model::AggregateField>& aggregates,
+                         api::AggregateQueryCallback&& result_callback);
 
   /** Returns true if the given error is a gRPC ABORTED error. */
   static bool IsAbortedError(const util::Status& error);
@@ -182,10 +184,12 @@ class Datastore : public std::enable_shared_from_this<Datastore> {
       const std::vector<model::DocumentKey>& keys,
       LookupCallback&& user_callback);
 
-  void RunCountQueryWithCredentials(const credentials::AuthToken& auth_token,
-                                    const std::string& app_check_token,
-                                    const core::Query& query,
-                                    api::CountQueryCallback&& callback);
+  void RunAggregateQueryWithCredentials(
+      const credentials::AuthToken& auth_token,
+      const std::string& app_check_token,
+      const core::Query& query,
+      const std::vector<model::AggregateField>& aggregates,
+      api::AggregateQueryCallback&& callback);
 
   using OnCredentials = std::function<void(
       const util::StatusOr<credentials::AuthToken>&, const std::string&)>;

+ 92 - 19
Firestore/core/src/remote/remote_objc_bridge.cc

@@ -20,6 +20,8 @@
 
 #include "Firestore/core/src/core/database_info.h"
 #include "Firestore/core/src/core/query.h"
+#include "Firestore/core/src/model/aggregate_alias.h"
+#include "Firestore/core/src/model/aggregate_field.h"
 #include "Firestore/core/src/model/document.h"
 #include "Firestore/core/src/model/document_key.h"
 #include "Firestore/core/src/model/mutation.h"
@@ -35,16 +37,21 @@
 #include "Firestore/core/src/util/statusor.h"
 #include "grpcpp/support/status.h"
 
+#include "absl/container/flat_hash_map.h"
+#include "absl/strings/str_format.h"
+
 namespace firebase {
 namespace firestore {
 namespace remote {
 
 using core::DatabaseInfo;
 using local::TargetData;
+using model::AggregateField;
 using model::Document;
 using model::DocumentKey;
 using model::Mutation;
 using model::MutationResult;
+using model::ObjectValue;
 using model::SnapshotVersion;
 using model::TargetId;
 using nanopb::ByteString;
@@ -265,8 +272,11 @@ DatastoreSerializer::MergeLookupResponses(
   return result;
 }
 
-nanopb::Message<google_firestore_v1_RunAggregationQueryRequest>
-DatastoreSerializer::EncodeCountQueryRequest(const core::Query& query) const {
+Message<google_firestore_v1_RunAggregationQueryRequest>
+DatastoreSerializer::EncodeAggregateQueryRequest(
+    const core::Query& query,
+    const std::vector<AggregateField>& aggregates,
+    absl::flat_hash_map<std::string, std::string>& aliasMap) const {
   Message<google_firestore_v1_RunAggregationQueryRequest> result;
   auto encodedTarget = serializer_.EncodeQueryTarget(query.ToTarget());
   result->parent = encodedTarget.parent;
@@ -278,22 +288,86 @@ DatastoreSerializer::EncodeCountQueryRequest(const core::Query& query) const {
   result->query_type.structured_aggregation_query.structured_query =
       encodedTarget.structured_query;
 
-  result->query_type.structured_aggregation_query.aggregations_count = 1;
+  // De-duplicate aggregates based on the alias.
+  // Since aliases are auto-computed from the operation and path,
+  // equal aggregate will have the same alias.
+  absl::flat_hash_map<std::string, AggregateField> uniqueAggregates;
+  for (const AggregateField& aggregate : aggregates) {
+    auto pair = std::pair<std::string, AggregateField>(
+        aggregate.alias.StringValue(), aggregate);
+    uniqueAggregates.insert(std::move(pair));
+  }
+
+  auto count = uniqueAggregates.size();
+  size_t aggregationNum = 0;
+  result->query_type.structured_aggregation_query.aggregations_count = count;
   result->query_type.structured_aggregation_query.aggregations =
-      MakeArray<_google_firestore_v1_StructuredAggregationQuery_Aggregation>(1);
-  result->query_type.structured_aggregation_query.aggregations[0].alias =
-      nanopb::MakeBytesArray("count_alias");
-  result->query_type.structured_aggregation_query.aggregations[0]
-      .which_operator =
-      google_firestore_v1_StructuredAggregationQuery_Aggregation_count_tag;
-  result->query_type.structured_aggregation_query.aggregations[0].count =
-      google_firestore_v1_StructuredAggregationQuery_Aggregation_Count{};
+      MakeArray<_google_firestore_v1_StructuredAggregationQuery_Aggregation>(
+          count);
+  for (const auto& aggregatePair : uniqueAggregates) {
+    // Map all client-side aliases to a unique short-form
+    // alias. This avoids issues with client-side aliases that
+    // exceed the 1500-byte string size limit.
+    std::string clientAlias = aggregatePair.first;
+    std::string serverAlias = absl::StrFormat("aggregation_%d", aggregationNum);
+    auto pair = std::pair<std::string, std::string>(serverAlias, clientAlias);
+    aliasMap.insert(std::move(pair));
+
+    // Send the server alias in the request to the backend
+    result->query_type.structured_aggregation_query.aggregations[aggregationNum]
+        .alias = nanopb::MakeBytesArray(serverAlias);
+
+    if (aggregatePair.second.op == AggregateField::OpKind::Count) {
+      result->query_type.structured_aggregation_query
+          .aggregations[aggregationNum]
+          .which_operator =
+          google_firestore_v1_StructuredAggregationQuery_Aggregation_count_tag;
+
+      result->query_type.structured_aggregation_query
+          .aggregations[aggregationNum]
+          .count =
+          google_firestore_v1_StructuredAggregationQuery_Aggregation_Count{};
+    } else if (aggregatePair.second.op == AggregateField::OpKind::Sum) {
+      google_firestore_v1_StructuredQuery_FieldReference field{};
+
+      field.field_path = nanopb::MakeBytesArray(
+          aggregatePair.second.fieldPath.CanonicalString());
+
+      result->query_type.structured_aggregation_query
+          .aggregations[aggregationNum]
+          .which_operator =
+          google_firestore_v1_StructuredAggregationQuery_Aggregation_sum_tag;
+
+      result->query_type.structured_aggregation_query
+          .aggregations[aggregationNum]
+          .sum =
+          google_firestore_v1_StructuredAggregationQuery_Aggregation_Sum{field};
+
+    } else if (aggregatePair.second.op == AggregateField::OpKind::Avg) {
+      google_firestore_v1_StructuredQuery_FieldReference field{};
+      field.field_path = nanopb::MakeBytesArray(
+          aggregatePair.second.fieldPath.CanonicalString());
+
+      result->query_type.structured_aggregation_query
+          .aggregations[aggregationNum]
+          .which_operator =
+          google_firestore_v1_StructuredAggregationQuery_Aggregation_avg_tag;
+
+      result->query_type.structured_aggregation_query
+          .aggregations[aggregationNum]
+          .avg =
+          google_firestore_v1_StructuredAggregationQuery_Aggregation_Avg{field};
+    }
+
+    ++aggregationNum;
+  }
 
   return result;
 }
 
-util::StatusOr<int64_t> DatastoreSerializer::DecodeCountQueryResponse(
-    const grpc::ByteBuffer& response) const {
+util::StatusOr<ObjectValue> DatastoreSerializer::DecodeAggregateQueryResponse(
+    const grpc::ByteBuffer& response,
+    const absl::flat_hash_map<std::string, std::string>& aliasMap) const {
   ByteBufferReader reader{response};
   auto message =
       Message<google_firestore_v1_RunAggregationQueryResponse>::TryParse(
@@ -302,12 +376,11 @@ util::StatusOr<int64_t> DatastoreSerializer::DecodeCountQueryResponse(
     return reader.status();
   }
 
-  HARD_ASSERT(message->result.aggregate_fields_count == 1);
-  HARD_ASSERT(nanopb::MakeStringView(message->result.aggregate_fields[0].key) ==
-              "count_alias");
-  HARD_ASSERT(message->result.aggregate_fields[0].value.which_value_type ==
-              google_firestore_v1_Value_integer_value_tag);
-  return message->result.aggregate_fields[0].value.integer_value;
+  HARD_ASSERT(message->result.aggregate_fields != nullptr);
+
+  return ObjectValue::FromAggregateFieldsEntry(
+      message->result.aggregate_fields, message->result.aggregate_fields_count,
+      aliasMap);
 }
 
 }  // namespace remote

+ 11 - 4
Firestore/core/src/remote/remote_objc_bridge.h

@@ -32,6 +32,8 @@
 #include "Firestore/core/src/util/status_fwd.h"
 #include "grpcpp/support/byte_buffer.h"
 
+#include "absl/container/flat_hash_map.h"
+
 namespace firebase {
 namespace firestore {
 
@@ -40,6 +42,7 @@ class TargetData;
 }  // namespace local
 
 namespace model {
+class AggregateField;
 class DocumentKey;
 class SnapshotVersion;
 }  // namespace model
@@ -134,10 +137,14 @@ class DatastoreSerializer {
       const std::vector<grpc::ByteBuffer>& responses) const;
 
   nanopb::Message<google_firestore_v1_RunAggregationQueryRequest>
-  EncodeCountQueryRequest(const core::Query& query) const;
-
-  util::StatusOr<int64_t> DecodeCountQueryResponse(
-      const grpc::ByteBuffer& response) const;
+  EncodeAggregateQueryRequest(
+      const core::Query& query,
+      const std::vector<model::AggregateField>& aggregates,
+      absl::flat_hash_map<std::string, std::string>& aliasMap) const;
+
+  util::StatusOr<model::ObjectValue> DecodeAggregateQueryResponse(
+      const grpc::ByteBuffer& response,
+      const absl::flat_hash_map<std::string, std::string>& aliasMap) const;
 
   const Serializer& serializer() const {
     return serializer_;

+ 7 - 3
Firestore/core/src/remote/remote_store.cc

@@ -39,6 +39,7 @@ using core::Transaction;
 using local::LocalStore;
 using local::QueryPurpose;
 using local::TargetData;
+using model::AggregateField;
 using model::BatchId;
 using model::DocumentKeySet;
 using model::kBatchIdUnknown;
@@ -365,10 +366,13 @@ void RemoteStore::ProcessTargetError(const WatchTargetChange& change) {
   }
 }
 
-void RemoteStore::RunCountQuery(const core::Query& query,
-                                api::CountQueryCallback&& result_callback) {
+void RemoteStore::RunAggregateQuery(
+    const core::Query& query,
+    const std::vector<AggregateField>& aggregates,
+    api::AggregateQueryCallback&& result_callback) {
   if (CanUseNetwork()) {
-    datastore_->RunCountQuery(query, std::move(result_callback));
+    datastore_->RunAggregateQuery(query, aggregates,
+                                  std::move(result_callback));
   } else {
     result_callback(Status::FromErrno(Error::kErrorUnavailable,
                                       "Failed to get result from server."));

+ 7 - 2
Firestore/core/src/remote/remote_store.h

@@ -42,6 +42,10 @@ namespace local {
 class LocalStore;
 }  // namespace local
 
+namespace model {
+class AggregateField;
+}  // namespace model
+
 namespace remote {
 
 class ConnectivityMonitor;
@@ -194,8 +198,9 @@ class RemoteStore : public TargetMetadataProvider,
   absl::optional<local::TargetData> GetTargetDataForTarget(
       model::TargetId target_id) const override;
 
-  void RunCountQuery(const core::Query& query,
-                     api::CountQueryCallback&& result_callback);
+  void RunAggregateQuery(const core::Query& query,
+                         const std::vector<model::AggregateField>& aggregates,
+                         api::AggregateQueryCallback&& result_callback);
 
   void OnWatchStreamOpen() override;
   void OnWatchStreamChange(

+ 1 - 0
Firestore/core/test/unit/api/CMakeLists.txt

@@ -21,5 +21,6 @@ firebase_ios_add_test(firestore_api_test ${sources})
 
 target_link_libraries(
   firestore_api_test PRIVATE
+  GMock::GMock
   firestore_core
 )

+ 137 - 1
Firestore/core/test/unit/api/aggregate_query_test.cc

@@ -14,21 +14,58 @@
  * limitations under the License.
  */
 
+// TODO(b/280805906) Remove these tests for the count specific API after the c++
+// SDK migrates to the new Aggregate API
+
 #include <memory>
+#include <string>
+#include <utility>
 
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 #include "Firestore/core/src/api/aggregate_query.h"
 #include "Firestore/core/src/api/firestore.h"
+#include "Firestore/core/src/api/query_core.h"
 #include "Firestore/core/src/core/query.h"
 #include "Firestore/core/src/model/resource_path.h"
 #include "Firestore/core/src/remote/firebase_metadata_provider.h"
+#include "Firestore/core/src/util/status.h"
 
 namespace firebase {
 namespace firestore {
+
+using model::AggregateAlias;
+using ::testing::Invoke;
+using util::Status;
+
 namespace api {
-namespace {
 
+class MockAggregateQuery : public AggregateQuery {
+ public:
+  using AggregateQuery::AggregateQuery;
+
+  ~MockAggregateQuery() override = default;
+
+  MOCK_METHOD(void,
+              GetAggregate,
+              (AggregateQueryCallback && callback),
+              (override));
+};
+
+class AggregateQueryTest {
+ public:
+  static const Query& GetQuery(const AggregateQuery& aggregate_query) {
+    return aggregate_query.query_;
+  }
+
+  static const std::vector<AggregateField>& GetAggregates(
+      const AggregateQuery& aggregate_query) {
+    return aggregate_query.aggregates_;
+  }
+};
+
+namespace {
 TEST(AggregateQuery, Equality) {
   {
     auto firestore = std::make_shared<Firestore>();
@@ -60,6 +97,105 @@ TEST(AggregateQuery, GetQuery) {
   }
 }
 
+// Assert that the Get member function calls GetAggregate member function
+// and that the result from GetAggregate is processed
+// appropriately
+TEST(AggregateQuery, GetCallsGetAggregateOk) {
+  // Test aggregate field result
+  google_firestore_v1_AggregationResult_AggregateFieldsEntry
+      aggregate_fields_entry[1];
+  aggregate_fields_entry[0].key = nanopb::ByteString("aggregate_0").release();
+  aggregate_fields_entry[0].value.which_value_type =
+      google_firestore_v1_Value_integer_value_tag;
+  aggregate_fields_entry[0].value.integer_value = 10;
+
+  // Test alias map
+  absl::flat_hash_map<std::string, std::string> alias_map;
+  alias_map["aggregate_0"] = "count";
+
+  // Test ObjectValue result
+  ObjectValue object_value_result = ObjectValue::FromAggregateFieldsEntry(
+      aggregate_fields_entry, 1, alias_map);
+
+  // Create an AggregateQuery with mocked GetAggregate function that
+  // invokes the callback with the test results from above
+  AggregateField count_aggregate_field(AggregateField::OpKind::Count,
+                                       AggregateAlias("count"));
+  std::vector<AggregateField> aggregates{count_aggregate_field};
+  MockAggregateQuery mock_aggregate_query({}, std::move(aggregates));
+  EXPECT_CALL(mock_aggregate_query, GetAggregate)
+      .Times(1)
+      .WillOnce(Invoke([object_value_result = std::move(object_value_result)](
+                           AggregateQueryCallback&& callback) {
+        callback(object_value_result);
+      }));
+
+  // Call the Get function, which is the function under test
+  int callback_count = 0;
+  mock_aggregate_query.Get(
+      [&callback_count](const StatusOr<int64_t>& result) mutable {
+        callback_count++;
+        ASSERT_EQ(result.ok(), true);
+        EXPECT_EQ(result.ValueOrDie(), 10);
+      });
+
+  // Assert the callback was invoked
+  EXPECT_EQ(callback_count, 1);
+}
+
+// Assert that the Get member function calls GetAggregate member function
+// and that an error result from GetAggregate is processed
+// appropriately
+TEST(AggregateQuery, GetCallsGetAggregateError) {
+  // Error result
+  Status error_result = Status(Error::kErrorInternal, "foo");
+
+  // Create an AggregateQuery with mocked GetAggregate function that
+  // invokes the callback with the error status from above
+  AggregateField count_aggregate_field(AggregateField::OpKind::Count,
+                                       AggregateAlias("count"));
+  std::vector<AggregateField> aggregates{count_aggregate_field};
+  MockAggregateQuery mock_aggregate_query({}, std::move(aggregates));
+  EXPECT_CALL(mock_aggregate_query, GetAggregate)
+      .Times(1)
+      .WillOnce(Invoke(
+          [error_result = std::move(error_result)](
+              AggregateQueryCallback&& callback) { callback(error_result); }));
+
+  // Call the Get member function
+  int callback_count = 0;
+  mock_aggregate_query.Get(
+      [&callback_count](const StatusOr<int64_t>& result) mutable {
+        callback_count++;
+        ASSERT_EQ(result.ok(), false);
+        EXPECT_EQ(result.status().code(), Error::kErrorInternal);
+        EXPECT_EQ(result.status().error_message(), "foo");
+      });
+
+  // Assert
+  EXPECT_EQ(callback_count, 1);
+}
+
+// Assert that the Query::Count member function creates an AggregateQuery
+// with the expected query and aggregates
+TEST(Query, Count) {
+  // Baseline Query
+  Query query;
+
+  // Testing the Count() function
+  AggregateQuery aggregate_query = query.Count();
+
+  const Query& internal_query = AggregateQueryTest::GetQuery(aggregate_query);
+  const std::vector<AggregateField>& internal_aggregates =
+      AggregateQueryTest::GetAggregates(aggregate_query);
+
+  // Assert
+  EXPECT_EQ(internal_query, query);
+  ASSERT_EQ(internal_aggregates.size(), 1);
+  EXPECT_EQ(internal_aggregates[0].op, AggregateField::OpKind::Count);
+  EXPECT_EQ(internal_aggregates[0].alias.StringValue(), "count");
+}
+
 }  // namespace
 
 }  // namespace api

+ 6 - 1
scripts/run_firestore_emulator.sh

@@ -20,7 +20,12 @@
 
 set -euo pipefail
 
-VERSION='1.14.4'
+# Use Java 11 if it is available on the runner image
+if [[ ! -z "${JAVA_HOME_11_X64:-}" ]]; then
+  export JAVA_HOME=$JAVA_HOME_11_X64
+fi
+
+VERSION='1.17.4'
 FILENAME="cloud-firestore-emulator-v${VERSION}.jar"
 URL="https://storage.googleapis.com/firebase-preview-drop/emulator/${FILENAME}"
 

Some files were not shown because too many files changed in this diff