From a97437af52f93e2e9c12d81a1cfd904e23e1d6be Mon Sep 17 00:00:00 2001 From: Jiun Kim Date: Tue, 1 Apr 2025 19:02:47 +0900 Subject: [PATCH 1/6] feat: Add AggregateField --- .../firebase/firestore/aggregate_field.h | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 firestore/src/include/firebase/firestore/aggregate_field.h diff --git a/firestore/src/include/firebase/firestore/aggregate_field.h b/firestore/src/include/firebase/firestore/aggregate_field.h new file mode 100644 index 0000000000..2618b81681 --- /dev/null +++ b/firestore/src/include/firebase/firestore/aggregate_field.h @@ -0,0 +1,42 @@ +#ifndef FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_AGGREGATE_FIELD_H_ +#define FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_AGGREGATE_FIELD_H_ + +#include +#include +#include "firebase/firestore/field_path.h" + +namespace firebase { +namespace firestore { + +/** + * @brief Represents a field to be used in an aggregation operation. + */ +class AggregateField { + public: + /** + * @brief Enum representing the type of aggregation. + */ + enum class Type { + kCount, + kSum, + // TODO: Add kAverage + }; + + // Constructor to initialize the AggregateField with a type and optional field path. + AggregateField(Type type, const FieldPath& field_path = FieldPath()); + + // Getter for the aggregation type. + Type type() const; + + // Getter for the associated field path. + const FieldPath& field_path() const; + + private: + Type type_; // The type of aggregation. + FieldPath field_path_; // The field path associated with the aggregation. +}; + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_AGGREGATE_FIELD_H_ From 2d2ecf2bb983afc3968cd937f1fd9a221a988959 Mon Sep 17 00:00:00 2001 From: Jiun Kim Date: Tue, 1 Apr 2025 19:05:06 +0900 Subject: [PATCH 2/6] feat: Add Aggregate method to Query --- firestore/src/common/query.cc | 6 ++++++ firestore/src/include/firebase/firestore/query.h | 16 ++++++++++++++++ firestore/src/main/query_main.cc | 5 +++++ firestore/src/main/query_main.h | 2 ++ 4 files changed, 29 insertions(+) diff --git a/firestore/src/common/query.cc b/firestore/src/common/query.cc index 089fc9375f..4286fb36bf 100644 --- a/firestore/src/common/query.cc +++ b/firestore/src/common/query.cc @@ -113,6 +113,12 @@ AggregateQuery Query::Count() const { return internal_->Count(); } +AggregateQuery Query::Aggregate(const AggregateField& aggregate_field) const { + if (!internal_) return {}; + return internal_->Aggregate(aggregate_field); +} + + Query Query::Where(const Filter& filter) const { if (!internal_) return {}; if (filter.IsEmpty()) { diff --git a/firestore/src/include/firebase/firestore/query.h b/firestore/src/include/firebase/firestore/query.h index 9cc7d5fe97..a89a951ba1 100644 --- a/firestore/src/include/firebase/firestore/query.h +++ b/firestore/src/include/firebase/firestore/query.h @@ -160,6 +160,22 @@ class Query { */ virtual AggregateQuery Count() const; + /** + * @brief Returns a query that calculates the specified aggregation over the + * documents in the result set of this query. + * + * The returned query, when executed, performs the specified aggregation + * without downloading the actual document data. + * + * Using the returned query to calculate aggregations is efficient because + * only the aggregation result, not the documents' data, is downloaded. This + * can be useful for aggregations over large result sets. + * + * @param aggregate_field The field to aggregate on. + * @return An aggregate query that performs the specified aggregation. + */ + virtual AggregateQuery Aggregate(const AggregateField& aggregate_field) const; + /** * @brief Creates and returns a new Query with the additional filter. * diff --git a/firestore/src/main/query_main.cc b/firestore/src/main/query_main.cc index a5d371a15e..78b3f20ebc 100644 --- a/firestore/src/main/query_main.cc +++ b/firestore/src/main/query_main.cc @@ -99,6 +99,11 @@ Future QueryInternal::Get(Source source) { AggregateQuery QueryInternal::Count() { return MakePublic(query_.Count()); } +AggregateQuery QueryInternal::Aggregate(const AggregateField& aggregate_field) { + // TODO: Implement Aggregate + // return MakePublic(aggregate_field); +} + Query QueryInternal::Where(const Filter& filter) const { SIMPLE_HARD_ASSERT(!filter.IsEmpty()); core::Filter core_filter = diff --git a/firestore/src/main/query_main.h b/firestore/src/main/query_main.h index ddf773f846..babd43c8bd 100644 --- a/firestore/src/main/query_main.h +++ b/firestore/src/main/query_main.h @@ -60,6 +60,8 @@ class QueryInternal { AggregateQuery Count(); + AggregateQuery Aggregate(const AggregateField& aggregate_field); + ListenerRegistration AddSnapshotListener( MetadataChanges metadata_changes, EventListener* listener); From c83c27b4190b1c93bf67f45c360dadd3d8abd1c7 Mon Sep 17 00:00:00 2001 From: Jiun Kim Date: Tue, 1 Apr 2025 19:05:30 +0900 Subject: [PATCH 3/6] feat: Add Aggregate method to QueryInternal --- firestore/src/android/query_android.cc | 7 +++++++ firestore/src/android/query_android.h | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/firestore/src/android/query_android.cc b/firestore/src/android/query_android.cc index 436e12c0a2..d386a91a68 100644 --- a/firestore/src/android/query_android.cc +++ b/firestore/src/android/query_android.cc @@ -162,6 +162,13 @@ AggregateQuery QueryInternal::Count() const { return firestore_->NewAggregateQuery(env, aggregate_query); } +AggregateQuery QueryInternal::Aggregate(const FieldPath& field) const { + Env env = GetEnv(); + Local java_field = FieldPathConverter::Create(env, field); + Local aggregate_query = env.Call(obj_, kAggregate, java_field); + return firestore_->NewAggregateQuery(env, aggregate_query); +} + Query QueryInternal::Where(const firebase::firestore::Filter& filter) const { Env env = GetEnv(); Local query = env.Call(obj_, kWhere, filter.internal_->ToJava()); diff --git a/firestore/src/android/query_android.h b/firestore/src/android/query_android.h index a325e561e4..a2eb6e5924 100644 --- a/firestore/src/android/query_android.h +++ b/firestore/src/android/query_android.h @@ -71,6 +71,15 @@ class QueryInternal : public Wrapper { */ virtual AggregateQuery Count() const; + /** + * @brief Returns a query that calculates the specified aggregation over the + * documents in the result set of this query. + * + * @param[in] aggregate_field The aggregation field. + * @return An aggregate query that performs the specified aggregation. + */ + virtual AggregateQuery Aggregate(const AggregateField& aggregate_field) const; + /** * @brief Creates and returns a new Query with the additional filter. * From f89d482a92227efd66a889fffc0e158176bf63f2 Mon Sep 17 00:00:00 2001 From: Jiun Kim Date: Tue, 1 Apr 2025 19:06:04 +0900 Subject: [PATCH 4/6] feat: Add sum method to AggregateQuerySnapshot --- firestore/src/android/aggregate_query_android.h | 3 ++- .../android/aggregate_query_snapshot_android.h | 1 + firestore/src/common/aggregate_query_snapshot.cc | 5 +++++ .../firestore/aggregate_query_snapshot.h | 8 ++++++++ firestore/src/main/aggregate_query_main.h | 1 + .../src/main/aggregate_query_snapshot_main.cc | 16 ++++++++++++++-- .../src/main/aggregate_query_snapshot_main.h | 6 ++++++ 7 files changed, 37 insertions(+), 3 deletions(-) diff --git a/firestore/src/android/aggregate_query_android.h b/firestore/src/android/aggregate_query_android.h index 7545d62f72..9b2936bb06 100644 --- a/firestore/src/android/aggregate_query_android.h +++ b/firestore/src/android/aggregate_query_android.h @@ -33,7 +33,8 @@ class AggregateQueryInternal : public Wrapper { // Firestore Future manager. enum class AsyncFn { kGet = 0, - kCount, // Must be the last enum value. + kCount, + kSum, // Must be the last enum value. }; static void Initialize(jni::Loader& loader); diff --git a/firestore/src/android/aggregate_query_snapshot_android.h b/firestore/src/android/aggregate_query_snapshot_android.h index 6ebad41a3e..8c2739c48a 100644 --- a/firestore/src/android/aggregate_query_snapshot_android.h +++ b/firestore/src/android/aggregate_query_snapshot_android.h @@ -34,6 +34,7 @@ class AggregateQuerySnapshotInternal : public Wrapper { AggregateQuery query() const; int64_t count() const; + double sum(const AggregateField& aggregate_field) const; std::size_t Hash() const; diff --git a/firestore/src/common/aggregate_query_snapshot.cc b/firestore/src/common/aggregate_query_snapshot.cc index a4ce874f33..3013c33a7e 100644 --- a/firestore/src/common/aggregate_query_snapshot.cc +++ b/firestore/src/common/aggregate_query_snapshot.cc @@ -103,6 +103,11 @@ int64_t AggregateQuerySnapshot::count() const { return internal_->count(); } +double AggregateQuerySnapshot::sum(const AggregateField& aggregate_field) const { + if (!internal_) return 0; + return internal_->sum(aggregate_field); +} + bool operator==(const AggregateQuerySnapshot& lhs, const AggregateQuerySnapshot& rhs) { return EqualityCompare(lhs.internal_, rhs.internal_); diff --git a/firestore/src/include/firebase/firestore/aggregate_query_snapshot.h b/firestore/src/include/firebase/firestore/aggregate_query_snapshot.h index bc1810c4ac..69563e697b 100644 --- a/firestore/src/include/firebase/firestore/aggregate_query_snapshot.h +++ b/firestore/src/include/firebase/firestore/aggregate_query_snapshot.h @@ -108,6 +108,14 @@ class AggregateQuerySnapshot { */ virtual int64_t count() const; + /** + * @brief Returns the sum of the specified field across all documents in the + * result set of the underlying query. + * + * @return The sum of the specified field across all documents. + */ + virtual double sum(const AggregateField& aggregate_field) const; + /** * @brief Returns true if this `AggregateQuerySnapshot` is valid, false if it * is not valid. An invalid `AggregateQuerySnapshot` could be the result of: diff --git a/firestore/src/main/aggregate_query_main.h b/firestore/src/main/aggregate_query_main.h index 3fb47cb095..c3fe519b68 100644 --- a/firestore/src/main/aggregate_query_main.h +++ b/firestore/src/main/aggregate_query_main.h @@ -50,6 +50,7 @@ class AggregateQueryInternal { enum class AsyncApis { kGet, kCount, + kSum, }; friend class AggregateQuerySnapshotTest; diff --git a/firestore/src/main/aggregate_query_snapshot_main.cc b/firestore/src/main/aggregate_query_snapshot_main.cc index 83408c3ff1..cba30c1e07 100644 --- a/firestore/src/main/aggregate_query_snapshot_main.cc +++ b/firestore/src/main/aggregate_query_snapshot_main.cc @@ -24,9 +24,15 @@ namespace firebase { namespace firestore { AggregateQuerySnapshotInternal::AggregateQuerySnapshotInternal( - api::AggregateQuery&& aggregate_query, int64_t count_result) + api::AggregateQuery&& aggregate_query, int64_t count_result, + // TODO: Update with sum + // double sum_result, + ) : aggregate_query_(std::move(aggregate_query)), - count_result_(count_result) {} + count_result_(count_result) + // TODO: Update with sum + // sum_result_(sum_result), + {} FirestoreInternal* AggregateQuerySnapshotInternal::firestore_internal() { return GetFirestoreInternal(&aggregate_query_.query()); @@ -43,10 +49,16 @@ AggregateQuery AggregateQuerySnapshotInternal::query() const { int64_t AggregateQuerySnapshotInternal::count() const { return count_result_; } +double AggregateQuerySnapshotInternal::sum(const AggregateField& aggregate_field) const { + // TODO: implement +} + bool operator==(const AggregateQuerySnapshotInternal& lhs, const AggregateQuerySnapshotInternal& rhs) { return lhs.aggregate_query_ == rhs.aggregate_query_ && lhs.count_result_ == rhs.count_result_; + // TODO: Update with sum + // && lhs.sum_result == rhs.sum_result_; } } // namespace firestore diff --git a/firestore/src/main/aggregate_query_snapshot_main.h b/firestore/src/main/aggregate_query_snapshot_main.h index 222c6b4e39..291a4abd46 100644 --- a/firestore/src/main/aggregate_query_snapshot_main.h +++ b/firestore/src/main/aggregate_query_snapshot_main.h @@ -35,15 +35,20 @@ class AggregateQuerySnapshotInternal { public: explicit AggregateQuerySnapshotInternal(api::AggregateQuery&& aggregate_query, int64_t count); + // TODO: Update with sum + // double sum); FirestoreInternal* firestore_internal(); const FirestoreInternal* firestore_internal() const; AggregateQuery query() const; int64_t count() const; + double sum(const AggregateField& aggregate_field) const; std::size_t Hash() const { return util::Hash(aggregate_query_.query().Hash(), count_result_); + // TODO: Update with sum + // , sum_result_); } friend bool operator==(const AggregateQuerySnapshotInternal& lhs, @@ -52,6 +57,7 @@ class AggregateQuerySnapshotInternal { private: api::AggregateQuery aggregate_query_; int64_t count_result_ = 0; + double sum_result_ = 0.0; }; inline bool operator!=(const AggregateQuerySnapshotInternal& lhs, From fae6142f5e08f09e6b66b56fdb6b175e8d60994e Mon Sep 17 00:00:00 2001 From: Jiun Kim Date: Tue, 1 Apr 2025 19:06:21 +0900 Subject: [PATCH 5/6] feat: Add test for Sum method in AggregateQuery --- .../src/aggregate_query_test.cc | 15 +++++++++++++++ .../src/aggregate_sum_test.cc | 17 +++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 firestore/integration_test_internal/src/aggregate_sum_test.cc diff --git a/firestore/integration_test_internal/src/aggregate_query_test.cc b/firestore/integration_test_internal/src/aggregate_query_test.cc index 4810e5e96f..0382fca795 100644 --- a/firestore/integration_test_internal/src/aggregate_query_test.cc +++ b/firestore/integration_test_internal/src/aggregate_query_test.cc @@ -337,6 +337,21 @@ TEST_F(AggregateQueryTest, TestHashCode) { AggregateQueryHash(query1.Count())); } +TEST_F(AggregateQueryTest, SumReturnsCorrectSum) { + CollectionReference collection = + Collection({{"a", {{"value", FieldValue::Integer(1)}}}, + {"b", {{"value", FieldValue::Integer(2)}}}, + {"c", {{"value", FieldValue::Integer(3)}}}}); + + AggregateQuery aggregate_query = + collection.Sum("value"); + AggregateQuerySnapshot snapshot = ReadAggregate(aggregate_query); + + EXPECT_EQ(6.0, snapshot.sum("value")); + EXPECT_EQ(aggregate_query, snapshot.query()); +} + + #if defined(__ANDROID__) TEST(QueryTestAndroidStub, Construction) { testutil::AssertWrapperConstructionContract(); diff --git a/firestore/integration_test_internal/src/aggregate_sum_test.cc b/firestore/integration_test_internal/src/aggregate_sum_test.cc new file mode 100644 index 0000000000..e8c5eaba7c --- /dev/null +++ b/firestore/integration_test_internal/src/aggregate_sum_test.cc @@ -0,0 +1,17 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// TODO: Add tests for AggregateQuery Sum From 1e734b97dc79f91b4c6ac12f5d67a92c0d64378c Mon Sep 17 00:00:00 2001 From: Jiun Kim Date: Tue, 1 Apr 2025 19:06:44 +0900 Subject: [PATCH 6/6] chore: Update release notes for upcoming release with Query::Sum() --- release_build_files/readme.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/release_build_files/readme.md b/release_build_files/readme.md index ae6d754b2c..5aa03ed701 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -631,6 +631,10 @@ workflow use only during the development of your app, not for publicly shipping code. ## Release Notes +### Upcoming Release +- Changes + - Firestore: Added `Query::Sum()`, which fetches the sum of numeric fields + in query results. ### 12.7.0 - Changes - General (iOS): Update to Firebase Cocoapods version 11.10.0. @@ -765,7 +769,7 @@ code. function to facilitate the [on-device conversion measurement](https://support.google.com/google-ads/answer/12119136) API. - Auth: Add Firebase Auth Emulator support. Set the environment variable - USE_AUTH_EMULATOR=yes (and optionally AUTH_EMULATOR_PORT, default 9099) + USE_AUTH_EMULATOR=yes (and optionally AUTH_EMULATOR_PORT, default 9099) to connect to the local Firebase Auth Emulator. - GMA (iOS): Updated dependency to Google-Mobile-Ads-SDK version 10.10.0. - GMA (Android): Updated dependency to play-services-ads version 22.3.0.