Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: Add AggregateField and Sum with Query #1705

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions firestore/integration_test_internal/src/aggregate_query_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<AggregateQuery>();
Expand Down
17 changes: 17 additions & 0 deletions firestore/integration_test_internal/src/aggregate_sum_test.cc
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion firestore/src/android/aggregate_query_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions firestore/src/android/aggregate_query_snapshot_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
7 changes: 7 additions & 0 deletions firestore/src/android/query_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<Object> java_field = FieldPathConverter::Create(env, field);
Local<Object> 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<Object> query = env.Call(obj_, kWhere, filter.internal_->ToJava());
Expand Down
9 changes: 9 additions & 0 deletions firestore/src/android/query_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
5 changes: 5 additions & 0 deletions firestore/src/common/aggregate_query_snapshot.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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_);
Expand Down
6 changes: 6 additions & 0 deletions firestore/src/common/query.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
42 changes: 42 additions & 0 deletions firestore/src/include/firebase/firestore/aggregate_field.h
Original file line number Diff line number Diff line change
@@ -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 <string>
#include <memory>
#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_
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
16 changes: 16 additions & 0 deletions firestore/src/include/firebase/firestore/query.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
1 change: 1 addition & 0 deletions firestore/src/main/aggregate_query_main.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class AggregateQueryInternal {
enum class AsyncApis {
kGet,
kCount,
kSum,
};

friend class AggregateQuerySnapshotTest;
Expand Down
16 changes: 14 additions & 2 deletions firestore/src/main/aggregate_query_snapshot_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions firestore/src/main/aggregate_query_snapshot_main.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions firestore/src/main/query_main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ Future<QuerySnapshot> 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 =
Expand Down
2 changes: 2 additions & 0 deletions firestore/src/main/query_main.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ class QueryInternal {

AggregateQuery Count();

AggregateQuery Aggregate(const AggregateField& aggregate_field);

ListenerRegistration AddSnapshotListener(
MetadataChanges metadata_changes, EventListener<QuerySnapshot>* listener);

Expand Down
6 changes: 5 additions & 1 deletion release_build_files/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down