diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 063323e707..50c0162a7d 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -522,6 +522,9 @@ if (IOS) ${FIREBASE_SOURCE_DIR}/dynamic_links/src/include/firebase/dynamic_links/components.h) set(firestore_HDRS ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore.h + ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/aggregate_query.h + ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/aggregate_query_snapshot.h + ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/aggregate_source.h ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/collection_reference.h ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/document_change.h ${FIREBASE_SOURCE_DIR}/firestore/src/include/firebase/firestore/document_reference.h diff --git a/firestore/CMakeLists.txt b/firestore/CMakeLists.txt index 1d9c9c5f07..b0a3b222c5 100644 --- a/firestore/CMakeLists.txt +++ b/firestore/CMakeLists.txt @@ -20,6 +20,8 @@ else() endif() set(common_SRCS + src/common/aggregate_query.cc + src/common/aggregate_query_snapshot.cc src/common/cleanup.h src/common/collection_reference.cc src/common/compiler_info.cc @@ -185,6 +187,10 @@ set(android_SRCS # Sources that apply to all non-Android platforms. set(main_SRCS + src/main/aggregate_query_main.cc + src/main/aggregate_query_main.h + src/main/aggregate_query_snapshot_main.cc + src/main/aggregate_query_snapshot_main.h src/main/collection_reference_main.cc src/main/collection_reference_main.h src/main/converter_main.h diff --git a/firestore/CONTRIBUTING.md b/firestore/CONTRIBUTING.md index 51cc290734..d7ad3455ea 100644 --- a/firestore/CONTRIBUTING.md +++ b/firestore/CONTRIBUTING.md @@ -19,7 +19,7 @@ a Python3 installation. # Architecture -It is easier to work this Firestore CPP SDK by keeping a high level archetecture in mind: +It is easier to work this Firestore CPP SDK by keeping a high level architecture in mind: ![architecture.png](architecture.png) diff --git a/firestore/src/common/aggregate_query.cc b/firestore/src/common/aggregate_query.cc new file mode 100644 index 0000000000..cb20323775 --- /dev/null +++ b/firestore/src/common/aggregate_query.cc @@ -0,0 +1,112 @@ +/* + * 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/src/include/firebase/firestore/aggregate_query.h" +#include "firestore/src/common/cleanup.h" +#include "firestore/src/common/futures.h" +#include "firestore/src/common/util.h" +#include "firestore/src/include/firebase/firestore/aggregate_query_snapshot.h" + +#if defined(__ANDROID__) +// TODO(tomandersen) #include "firestore/src/android/aggregate_query_android.h" +#else +#include "firestore/src/main/aggregate_query_main.h" +#endif // defined(__ANDROID__) + +namespace firebase { +namespace firestore { + +using CleanupFnAggregateQuery = CleanupFn; + +AggregateQuery::AggregateQuery() {} + +AggregateQuery::AggregateQuery(const AggregateQuery& other) { + if (other.internal_) { + internal_ = new AggregateQueryInternal(*other.internal_); + } + CleanupFnAggregateQuery::Register(this, internal_); +} + +AggregateQuery::AggregateQuery(AggregateQuery&& other) { + CleanupFnAggregateQuery::Unregister(&other, other.internal_); + std::swap(internal_, other.internal_); + CleanupFnAggregateQuery::Register(this, internal_); +} + +AggregateQuery::AggregateQuery(AggregateQueryInternal* internal) + : internal_(internal) { + SIMPLE_HARD_ASSERT(internal != nullptr); + CleanupFnAggregateQuery::Register(this, internal_); +} + +AggregateQuery::~AggregateQuery() { + CleanupFnAggregateQuery::Unregister(this, internal_); + delete internal_; + internal_ = nullptr; +} + +AggregateQuery& AggregateQuery::operator=(const AggregateQuery& other) { + if (this == &other) { + return *this; + } + + CleanupFnAggregateQuery::Unregister(this, internal_); + delete internal_; + if (other.internal_) { + internal_ = new AggregateQueryInternal(*other.internal_); + } else { + internal_ = nullptr; + } + CleanupFnAggregateQuery::Register(this, internal_); + return *this; +} + +AggregateQuery& AggregateQuery::operator=(AggregateQuery&& other) { + if (this == &other) { + return *this; + } + + CleanupFnAggregateQuery::Unregister(&other, other.internal_); + CleanupFnAggregateQuery::Unregister(this, internal_); + delete internal_; + internal_ = other.internal_; + other.internal_ = nullptr; + CleanupFnAggregateQuery::Register(this, internal_); + return *this; +} + +Query AggregateQuery::query() const { + if (!internal_) return {}; + return internal_->query(); +} + +Future AggregateQuery::Get( + AggregateSource aggregate_source) const { + if (!internal_) return FailedFuture(); + return internal_->Get(aggregate_source); +} + +size_t AggregateQuery::Hash() const { + if (!internal_) return {}; + return internal_->Hash(); +} + +bool operator==(const AggregateQuery& lhs, const AggregateQuery& rhs) { + return EqualityCompare(lhs.internal_, rhs.internal_); +} + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/common/aggregate_query_snapshot.cc b/firestore/src/common/aggregate_query_snapshot.cc new file mode 100644 index 0000000000..d86dd44863 --- /dev/null +++ b/firestore/src/common/aggregate_query_snapshot.cc @@ -0,0 +1,117 @@ +/* + * 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/src/include/firebase/firestore/aggregate_query_snapshot.h" + +#include "firestore/src/common/cleanup.h" +#include "firestore/src/common/util.h" +#include "firestore/src/include/firebase/firestore/aggregate_query.h" + +#if defined(__ANDROID__) +// TODO(tomandersen) #include +// "firestore/src/android/aggregate_query_snapshot_android.h" +#else +#include "firestore/src/main/aggregate_query_snapshot_main.h" +#endif // defined(__ANDROID__) + +namespace firebase { +namespace firestore { + +using CleanupFnAggregateQuerySnapshot = CleanupFn; + +AggregateQuerySnapshot::AggregateQuerySnapshot() {} + +AggregateQuerySnapshot::AggregateQuerySnapshot( + const AggregateQuerySnapshot& other) { + if (other.internal_) { + internal_ = new AggregateQuerySnapshotInternal(*other.internal_); + } + CleanupFnAggregateQuerySnapshot::Register(this, internal_); +} + +AggregateQuerySnapshot::AggregateQuerySnapshot(AggregateQuerySnapshot&& other) { + CleanupFnAggregateQuerySnapshot::Unregister(&other, other.internal_); + std::swap(internal_, other.internal_); + CleanupFnAggregateQuerySnapshot::Register(this, internal_); +} + +AggregateQuerySnapshot::AggregateQuerySnapshot( + AggregateQuerySnapshotInternal* internal) + : internal_(internal) { + SIMPLE_HARD_ASSERT(internal != nullptr); + CleanupFnAggregateQuerySnapshot::Register(this, internal_); +} + +AggregateQuerySnapshot::~AggregateQuerySnapshot() { + CleanupFnAggregateQuerySnapshot::Unregister(this, internal_); + delete internal_; + internal_ = nullptr; +} + +AggregateQuerySnapshot& AggregateQuerySnapshot::operator=( + const AggregateQuerySnapshot& other) { + if (this == &other) { + return *this; + } + + CleanupFnAggregateQuerySnapshot::Unregister(this, internal_); + delete internal_; + if (other.internal_) { + internal_ = new AggregateQuerySnapshotInternal(*other.internal_); + } else { + internal_ = nullptr; + } + CleanupFnAggregateQuerySnapshot::Register(this, internal_); + return *this; +} + +AggregateQuerySnapshot& AggregateQuerySnapshot::operator=( + AggregateQuerySnapshot&& other) { + if (this == &other) { + return *this; + } + + CleanupFnAggregateQuerySnapshot::Unregister(&other, other.internal_); + CleanupFnAggregateQuerySnapshot::Unregister(this, internal_); + delete internal_; + internal_ = other.internal_; + other.internal_ = nullptr; + CleanupFnAggregateQuerySnapshot::Register(this, internal_); + return *this; +} + +AggregateQuery AggregateQuerySnapshot::query() const { + if (!internal_) return {}; + return internal_->query(); +} + +int64_t AggregateQuerySnapshot::count() const { + if (!internal_) return 0; + return internal_->count(); +} + +bool operator==(const AggregateQuerySnapshot& lhs, + const AggregateQuerySnapshot& rhs) { + return EqualityCompare(lhs.internal_, rhs.internal_); +} + +size_t AggregateQuerySnapshot::Hash() const { + if (!internal_) return {}; + return internal_->Hash(); +} + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/common/query.cc b/firestore/src/common/query.cc index de525c52cc..7edbdd7602 100644 --- a/firestore/src/common/query.cc +++ b/firestore/src/common/query.cc @@ -23,6 +23,7 @@ #include "firestore/src/common/futures.h" #include "firestore/src/common/hard_assert_common.h" #include "firestore/src/common/util.h" +#include "firestore/src/include/firebase/firestore/aggregate_query.h" #include "firestore/src/include/firebase/firestore/document_snapshot.h" #include "firestore/src/include/firebase/firestore/field_path.h" #include "firestore/src/include/firebase/firestore/field_value.h" @@ -106,6 +107,11 @@ Firestore* Query::firestore() { return internal_->firestore(); } +AggregateQuery Query::Count() const { + if (!internal_) return {}; + return internal_->Count(); +} + Query Query::WhereEqualTo(const std::string& field, const FieldValue& value) const { return WhereEqualTo(FieldPath::FromDotSeparatedString(field), value); diff --git a/firestore/src/common/type_mapping.h b/firestore/src/common/type_mapping.h index aedee57887..269b92a410 100644 --- a/firestore/src/common/type_mapping.h +++ b/firestore/src/common/type_mapping.h @@ -22,6 +22,10 @@ namespace firebase { namespace firestore { +class AggregateQuery; +class AggregateQueryInternal; +class AggregateQuerySnapshot; +class AggregateQuerySnapshotInternal; class CollectionReference; class CollectionReferenceInternal; class DocumentChange; @@ -54,6 +58,14 @@ class WriteBatchInternal; template struct InternalTypeMap {}; +template <> +struct InternalTypeMap { + using type = AggregateQueryInternal; +}; +template <> +struct InternalTypeMap { + using type = AggregateQuerySnapshotInternal; +}; template <> struct InternalTypeMap { using type = CollectionReferenceInternal; diff --git a/firestore/src/include/firebase/firestore.h b/firestore/src/include/firebase/firestore.h index c35de11b4d..1749a4e3b2 100644 --- a/firestore/src/include/firebase/firestore.h +++ b/firestore/src/include/firebase/firestore.h @@ -27,6 +27,9 @@ #include "firebase/log.h" // Include *all* the public headers to make sure including just "firestore.h" is // sufficient for users. +#include "firebase/firestore/aggregate_query.h" +#include "firebase/firestore/aggregate_query_snapshot.h" +#include "firebase/firestore/aggregate_source.h" #include "firebase/firestore/collection_reference.h" #include "firebase/firestore/document_change.h" #include "firebase/firestore/document_reference.h" diff --git a/firestore/src/include/firebase/firestore/aggregate_query.h b/firestore/src/include/firebase/firestore/aggregate_query.h new file mode 100644 index 0000000000..035d923af7 --- /dev/null +++ b/firestore/src/include/firebase/firestore/aggregate_query.h @@ -0,0 +1,158 @@ +/* + * 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 FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_AGGREGATE_QUERY_H_ +#define FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_AGGREGATE_QUERY_H_ + +#include "firebase/firestore/aggregate_source.h" + +#include + +namespace firebase { +/// @cond FIREBASE_APP_INTERNAL +template +class Future; +/// @endcond + +namespace firestore { + +class AggregateQueryInternal; +class AggregateQuerySnapshot; +class Query; + +/** + * @brief A query that calculates aggregations over an underlying query. + */ +class AggregateQuery { + public: + /** + * @brief Creates an invalid AggregateQuery that has to be reassigned before + * it can be used. + * + * Calling any member function on an invalid AggregateQuery will be a no-op. + * If the function returns a value, it will return a zero, empty, or invalid + * value, depending on the type of the value. + */ + AggregateQuery(); + + /** + * @brief Copy constructor. + * + * `AggregateQuery` is immutable and can be efficiently copied (no deep copy + * is performed). + * + * @param[in] other `AggregateQuery` to copy from. + */ + AggregateQuery(const AggregateQuery& other); + + /** + * @brief Move constructor. + * + * Moving is more efficient than copying for a `AggregateQuery`. After being + * moved from, a `AggregateQuery` is equivalent to its default-constructed + * state. + * + * @param[in] other `AggregateQuery` to move data from. + */ + AggregateQuery(AggregateQuery&& other); + + virtual ~AggregateQuery(); + + /** + * @brief Copy assignment operator. + * + * `AggregateQuery` is immutable and can be efficiently copied (no deep copy + * is performed). + * + * @param[in] other `AggregateQuery` to copy from. + * + * @return Reference to the destination `AggregateQuery`. + */ + AggregateQuery& operator=(const AggregateQuery& other); + + /** + * @brief Move assignment operator. + * + * Moving is more efficient than copying for a `AggregateQuery`. After being + * moved from, a `AggregateQuery` is equivalent to its default-constructed + * state. + * + * @param[in] other `AggregateQuery` to move data from. + * + * @return Reference to the destination `AggregateQuery`. + */ + AggregateQuery& operator=(AggregateQuery&& other); + + /** + * @brief Returns the query whose aggregations will be calculated by this + * object. + */ + virtual Query query() const; + + /** + * @brief Executes this query. + * + * @param[in] aggregate_source The source from which to acquire the aggregate + * results. + * + * @return A Future that will be resolved with the results of the + * AggregateQuery. + */ + virtual Future Get( + AggregateSource aggregate_source) const; + + /** + * @brief Returns true if this `AggregateQuery` is valid, false if it is not + * valid. An invalid `AggregateQuery` could be the result of: + * - Creating a `AggregateQuery` using the default constructor. + * - Moving from the `AggregateQuery`. + * - Deleting your Firestore instance, which will invalidate all the + * `AggregateQuery` instances associated with it. + * + * @return true if this `AggregateQuery` is valid, false if this + * `AggregateQuery` is invalid. + */ + bool is_valid() const { return internal_ != nullptr; } + + private: + std::size_t Hash() const; + + friend class AggregateQueryInternal; + friend struct ConverterImpl; + + friend bool operator==(const AggregateQuery& lhs, const AggregateQuery& rhs); + friend std::size_t AggregateQueryHash(const AggregateQuery& aggregate_query); + + template + friend struct CleanupFn; + + explicit AggregateQuery(AggregateQueryInternal* internal); + + mutable AggregateQueryInternal* internal_ = nullptr; +}; + +/** Checks `lhs` and `rhs` for equality. */ +bool operator==(const AggregateQuery& lhs, const AggregateQuery& rhs); + +/** Checks `lhs` and `rhs` for inequality. */ +inline bool operator!=(const AggregateQuery& lhs, const AggregateQuery& rhs) { + return !(lhs == rhs); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_AGGREGATE_QUERY_H_ diff --git a/firestore/src/include/firebase/firestore/aggregate_query_snapshot.h b/firestore/src/include/firebase/firestore/aggregate_query_snapshot.h new file mode 100644 index 0000000000..103d9c431d --- /dev/null +++ b/firestore/src/include/firebase/firestore/aggregate_query_snapshot.h @@ -0,0 +1,154 @@ +/* + * 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 FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_AGGREGATE_QUERY_SNAPSHOT_H_ +#define FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_AGGREGATE_QUERY_SNAPSHOT_H_ + +#include +#include + +namespace firebase { +namespace firestore { + +class AggregateQuery; +class AggregateQuerySnapshotInternal; + +/** + * @brief The results of executing an AggregateQuery. + * + * @note Firestore classes are not meant to be subclassed except for use in test + * mocks. Subclassing is not supported in production code and new SDK releases + * may break code that does so. + */ +class AggregateQuerySnapshot { + public: + /** + * @brief Creates an invalid AggregateQuerySnapshot that has to be reassigned + * before it can be used. + * + * Calling any member function on an invalid AggregateQuerySnapshot will be a + * no-op. If the function returns a value, it will return a zero, empty, or + * invalid value, depending on the type of the value. + */ + AggregateQuerySnapshot(); + + /** + * @brief Copy constructor. + * + * `AggregateQuerySnapshot` is immutable and can be efficiently copied (no + * deep copy is performed). + * + * @param[in] other `AggregateQuerySnapshot` to copy from. + */ + AggregateQuerySnapshot(const AggregateQuerySnapshot& other); + + /** + * @brief Move constructor. + * + * Moving is more efficient than copying for a `AggregateQuerySnapshot`. After + * being moved from, a `AggregateQuerySnapshot` is equivalent to its + * default-constructed state. + * + * @param[in] other `AggregateQuerySnapshot` to move data from. + */ + AggregateQuerySnapshot(AggregateQuerySnapshot&& other); + + virtual ~AggregateQuerySnapshot(); + + /** + * @brief Copy assignment operator. + * + * `AggregateQuerySnapshot` is immutable and can be efficiently copied (no + * deep copy is performed). + * + * @param[in] other `AggregateQuerySnapshot` to copy from. + * + * @return Reference to the destination `AggregateQuerySnapshot`. + */ + AggregateQuerySnapshot& operator=(const AggregateQuerySnapshot& other); + + /** + * @brief Move assignment operator. + * + * Moving is more efficient than copying for a `AggregateQuerySnapshot`. After + * being moved from, a `AggregateQuerySnapshot` is equivalent to its + * default-constructed state. + * + * @param[in] other `AggregateQuerySnapshot` to move data from. + * + * @return Reference to the destination `AggregateQuerySnapshot`. + */ + AggregateQuerySnapshot& operator=(AggregateQuerySnapshot&& other); + + /** + * @brief Returns the query that was executed to produce this result. + * + * @return The `AggregateQuery` instance. + */ + virtual AggregateQuery query() const; + + /** + * @brief Returns the number of documents in the result set of the underlying + * query. + * + * @return The number of documents in the result set of the underlying query. + */ + virtual int64_t count() const; + + /** + * @brief Returns true if this `AggregateQuerySnapshot` is valid, false if it + * is not valid. An invalid `AggregateQuerySnapshot` could be the result of: + * - Creating a `AggregateQuerySnapshot` using the default constructor. + * - Moving from the `AggregateQuery`. + * - Deleting your Firestore instance, which will invalidate all the + * `AggregateQuery` instances associated with it. + * + * @return true if this `AggregateQuery` is valid, false if this + * `AggregateQuerySnapshot` is invalid. + */ + bool is_valid() const; + + private: + std::size_t Hash() const; + + friend bool operator==(const AggregateQuerySnapshot& lhs, + const AggregateQuerySnapshot& rhs); + friend std::size_t AggregateQuerySnapshotHash( + const AggregateQuerySnapshot& snapshot); + friend struct ConverterImpl; + + template + friend struct CleanupFn; + + explicit AggregateQuerySnapshot(AggregateQuerySnapshotInternal* internal); + + mutable AggregateQuerySnapshotInternal* internal_ = nullptr; +}; + +/** Checks `lhs` and `rhs` for equality. */ +bool operator==(const AggregateQuerySnapshot& lhs, + const AggregateQuerySnapshot& rhs); + +/** Checks `lhs` and `rhs` for inequality. */ +inline bool operator!=(const AggregateQuerySnapshot& lhs, + const AggregateQuerySnapshot& rhs) { + return !(lhs == rhs); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_AGGREGATE_QUERY_SNAPSHOT_H_ diff --git a/firestore/src/include/firebase/firestore/aggregate_source.h b/firestore/src/include/firebase/firestore/aggregate_source.h new file mode 100644 index 0000000000..f6d88d9279 --- /dev/null +++ b/firestore/src/include/firebase/firestore/aggregate_source.h @@ -0,0 +1,46 @@ +/* + * 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 FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_AGGREGATE_SOURCE_H_ +#define FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_AGGREGATE_SOURCE_H_ + +namespace firebase { +namespace firestore { + +/** + * @brief The sources from which an AggregateQuery::Get can retrieve its + * results. + */ +enum class AggregateSource { + /** + * Perform the aggregation on the server and download the result. + * + * The result received from the server is presented, unaltered, without + * considering any local state. That is, documents in the local cache are not + * taken into consideration, neither are local modifications not yet + * synchronized with the server. Previously-downloaded results, if any, are + * not used: every request using this source necessarily involves a round trip + * to the server. + * + * The AggregateQuery will fail if the server cannot be reached, such as if + * the client is offline. + */ + kServer, +}; + +} // namespace firestore +} // namespace firebase +#endif // FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_AGGREGATE_SOURCE_H_ diff --git a/firestore/src/include/firebase/firestore/query.h b/firestore/src/include/firebase/firestore/query.h index d2e73400ce..1a6c0c398c 100644 --- a/firestore/src/include/firebase/firestore/query.h +++ b/firestore/src/include/firebase/firestore/query.h @@ -37,6 +37,7 @@ class Future; namespace firestore { +class AggregateQuery; class DocumentSnapshot; template class EventListener; @@ -143,6 +144,22 @@ class Query { */ virtual Firestore* firestore(); + /** + * @brief Returns a query that counts the documents in the result set of this + * query. + * + * The returned query, when executed, counts the documents in the result set + * of this query without actually downloading the documents. + * + * Using the returned query to count the documents is efficient because only + * the final count, not the documents' data, is downloaded. The returned query + * can even count the documents if the result set would be prohibitively large + * to download entirely (e.g. thousands of documents). + * + * @return A query that counts the documents in the result set of this query. + */ + virtual AggregateQuery Count() const; + /** * @brief Creates and returns a new Query with the additional filter that * documents must contain the specified field and the value should be equal to diff --git a/firestore/src/main/aggregate_query_main.cc b/firestore/src/main/aggregate_query_main.cc new file mode 100644 index 0000000000..607db0774b --- /dev/null +++ b/firestore/src/main/aggregate_query_main.cc @@ -0,0 +1,80 @@ +/* + * 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 + +#include "firestore/src/main/aggregate_query_main.h" + +#include "Firestore/core/src/api/aggregate_query.h" +#include "Firestore/core/src/api/query_core.h" +#include "firestore/src/include/firebase/firestore/aggregate_query.h" +#include "firestore/src/include/firebase/firestore/aggregate_query_snapshot.h" +#include "firestore/src/main/aggregate_query_snapshot_main.h" +#include "firestore/src/main/listener_main.h" +#include "firestore/src/main/promise_factory_main.h" +#include "firestore/src/main/util_main.h" + +namespace firebase { +namespace firestore { + +AggregateQueryInternal::AggregateQueryInternal( + api::AggregateQuery&& aggregate_query) + : aggregate_query_{std::move(aggregate_query)}, + promise_factory_{PromiseFactory::Create(this)} {} + +FirestoreInternal* AggregateQueryInternal::firestore_internal() { + return GetFirestoreInternal(&aggregate_query_.query()); +} + +const FirestoreInternal* AggregateQueryInternal::firestore_internal() const { + return GetFirestoreInternal(&aggregate_query_.query()); +} + +Query AggregateQueryInternal::query() { + return MakePublic(api::Query(aggregate_query_.query())); +} + +Future AggregateQueryInternal::Get( + AggregateSource source) { + api::AggregateQuery aggregate_query = aggregate_query_; + auto promise = + promise_factory_.CreatePromise(AsyncApis::kGet); + // TODO(C++14) + // https://en.wikipedia.org/wiki/C%2B%2B14#Lambda_capture_expressions + aggregate_query_.Get( + [aggregate_query, promise](util::StatusOr maybe_value) mutable { + if (maybe_value.ok()) { + int64_t count = maybe_value.ValueOrDie(); + AggregateQuerySnapshotInternal internal{std::move(aggregate_query), + count}; + AggregateQuerySnapshot snapshot = MakePublic(std::move(internal)); + promise.SetValue(std::move(snapshot)); + } else { + promise.SetError(maybe_value.status()); + } + }); + return promise.future(); +} + +bool operator==(const AggregateQueryInternal& lhs, + const AggregateQueryInternal& rhs) { + // TODO(tomandersen) - there needs to be equals operator defined on + // api::AggregateQuery + return lhs.aggregate_query_.query() == rhs.aggregate_query_.query(); +} + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/main/aggregate_query_main.h b/firestore/src/main/aggregate_query_main.h new file mode 100644 index 0000000000..defe03a06f --- /dev/null +++ b/firestore/src/main/aggregate_query_main.h @@ -0,0 +1,65 @@ +/* + * 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 FIREBASE_FIRESTORE_SRC_MAIN_AGGREGATE_QUERY_MAIN_H_ +#define FIREBASE_FIRESTORE_SRC_MAIN_AGGREGATE_QUERY_MAIN_H_ + +#include "Firestore/core/src/api/aggregate_query.h" +#include "firestore/src/include/firebase/firestore/aggregate_query.h" +#include "firestore/src/main/firestore_main.h" + +#if defined(__ANDROID__) +#error "This header should not be used on Android." +#endif + +namespace firebase { +namespace firestore { + +class AggregateQueryInternal { + public: + explicit AggregateQueryInternal(api::AggregateQuery&& aggregate_query); + + FirestoreInternal* firestore_internal(); + const FirestoreInternal* firestore_internal() const; + + Query query(); + + Future Get(AggregateSource source); + + size_t Hash() const { return aggregate_query_.query().Hash(); } + + friend bool operator==(const AggregateQueryInternal& lhs, + const AggregateQueryInternal& rhs); + + private: + enum class AsyncApis { + kGet, + kCount, + }; + + api::AggregateQuery aggregate_query_; + PromiseFactory promise_factory_; +}; + +inline bool operator!=(const AggregateQueryInternal& lhs, + const AggregateQueryInternal& rhs) { + return !(lhs == rhs); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_MAIN_AGGREGATE_QUERY_MAIN_H_ diff --git a/firestore/src/main/aggregate_query_snapshot_main.cc b/firestore/src/main/aggregate_query_snapshot_main.cc new file mode 100644 index 0000000000..3cb427f243 --- /dev/null +++ b/firestore/src/main/aggregate_query_snapshot_main.cc @@ -0,0 +1,55 @@ +/* + * 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/src/main/aggregate_query_snapshot_main.h" + +#include +#include "firestore/src/main/converter_main.h" +#include "firestore/src/main/util_main.h" + +namespace firebase { +namespace firestore { + +AggregateQuerySnapshotInternal::AggregateQuerySnapshotInternal( + api::AggregateQuery&& aggregate_query, int64_t count_result) + : aggregate_query_(std::move(aggregate_query)), + count_result_(count_result) {} + +FirestoreInternal* AggregateQuerySnapshotInternal::firestore_internal() { + return GetFirestoreInternal(&aggregate_query_.query()); +} + +const FirestoreInternal* AggregateQuerySnapshotInternal::firestore_internal() + const { + return GetFirestoreInternal(&aggregate_query_.query()); +} + +AggregateQuery AggregateQuerySnapshotInternal::query() const { + return MakePublic(api::AggregateQuery(aggregate_query_)); +} + +int64_t AggregateQuerySnapshotInternal::count() const { return count_result_; } + +bool operator==(const AggregateQuerySnapshotInternal& lhs, + const AggregateQuerySnapshotInternal& rhs) { + // TODO(tomandersen) - there needs to be equals operator defined on + // api::AggregateQuery + return lhs.aggregate_query_.query() == rhs.aggregate_query_.query() && + lhs.count_result_ == rhs.count_result_; +} + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/main/aggregate_query_snapshot_main.h b/firestore/src/main/aggregate_query_snapshot_main.h new file mode 100644 index 0000000000..336903d1ec --- /dev/null +++ b/firestore/src/main/aggregate_query_snapshot_main.h @@ -0,0 +1,65 @@ +/* + * 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 FIREBASE_FIRESTORE_SRC_MAIN_AGGREGATE_QUERY_SNAPSHOT_MAIN_H_ +#define FIREBASE_FIRESTORE_SRC_MAIN_AGGREGATE_QUERY_SNAPSHOT_MAIN_H_ + +#include +#include + +#include "Firestore/core/src/api/aggregate_query.h" +#include "firestore/src/include/firebase/firestore/aggregate_query.h" +#include "firestore/src/main/firestore_main.h" + +#if defined(__ANDROID__) +#error "This header should not be used on Android." +#endif + +namespace firebase { +namespace firestore { + +class AggregateQuerySnapshotInternal { + public: + explicit AggregateQuerySnapshotInternal(api::AggregateQuery&& aggregate_query, + int64_t count); + + FirestoreInternal* firestore_internal(); + const FirestoreInternal* firestore_internal() const; + + AggregateQuery query() const; + int64_t count() const; + + std::size_t Hash() const { + return util::Hash(aggregate_query_.query().Hash(), count_result_); + } + + friend bool operator==(const AggregateQuerySnapshotInternal& lhs, + const AggregateQuerySnapshotInternal& rhs); + + private: + api::AggregateQuery aggregate_query_; + int64_t count_result_; +}; + +inline bool operator!=(const AggregateQuerySnapshotInternal& lhs, + const AggregateQuerySnapshotInternal& rhs) { + return !(lhs == rhs); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_MAIN_AGGREGATE_QUERY_SNAPSHOT_MAIN_H_ diff --git a/firestore/src/main/converter_main.h b/firestore/src/main/converter_main.h index 38dd62fb85..14dac38a59 100644 --- a/firestore/src/main/converter_main.h +++ b/firestore/src/main/converter_main.h @@ -20,6 +20,7 @@ #include #include +#include "Firestore/core/src/api/aggregate_query.h" #include "Firestore/core/src/api/collection_reference.h" #include "Firestore/core/src/api/document_change.h" #include "Firestore/core/src/api/document_reference.h" @@ -30,8 +31,12 @@ #include "Firestore/core/src/core/transaction.h" #include "Firestore/core/src/model/field_path.h" #include "absl/memory/memory.h" +#include "firebase/firestore/aggregate_query.h" +#include "firebase/firestore/aggregate_query_snapshot.h" #include "firestore/src/common/type_mapping.h" #include "firestore/src/include/firebase/firestore.h" +#include "firestore/src/main/aggregate_query_main.h" +#include "firestore/src/main/aggregate_query_snapshot_main.h" #include "firestore/src/main/collection_reference_main.h" #include "firestore/src/main/document_change_main.h" #include "firestore/src/main/document_reference_main.h" @@ -82,6 +87,10 @@ struct ConverterImpl { // MakePublic +inline AggregateQuery MakePublic(api::AggregateQuery&& from) { + return ConverterImpl::MakePublicFromCore(std::move(from)); +} + inline CollectionReference MakePublic(api::CollectionReference&& from) { return ConverterImpl::MakePublicFromCore( std::move(from)); @@ -103,6 +112,12 @@ inline FieldValue MakePublic(FieldValueInternal&& from) { return ConverterImpl::MakePublicFromInternal(std::move(from)); } +inline AggregateQuerySnapshot MakePublic( + AggregateQuerySnapshotInternal&& from) { + return ConverterImpl::MakePublicFromInternal( + std::move(from)); +} + inline ListenerRegistration MakePublic( std::unique_ptr from, FirestoreInternal* firestore) { diff --git a/firestore/src/main/query_main.cc b/firestore/src/main/query_main.cc index 070e113fc1..ffed058c9e 100644 --- a/firestore/src/main/query_main.cc +++ b/firestore/src/main/query_main.cc @@ -18,6 +18,7 @@ #include +#include "Firestore/core/src/api/aggregate_query.h" #include "Firestore/core/src/api/listener_registration.h" #include "Firestore/core/src/core/filter.h" #include "Firestore/core/src/core/listen_options.h" @@ -33,6 +34,7 @@ #include "firestore/src/common/hard_assert_common.h" #include "firestore/src/common/macros.h" #include "firestore/src/include/firebase/firestore.h" +#include "firestore/src/main/aggregate_query_main.h" #include "firestore/src/main/converter_main.h" #include "firestore/src/main/document_snapshot_main.h" #include "firestore/src/main/listener_main.h" @@ -94,6 +96,8 @@ Future QueryInternal::Get(Source source) { return promise.future(); } +AggregateQuery QueryInternal::Count() { return MakePublic(query_.Count()); } + Query QueryInternal::Where(const FieldPath& field_path, Operator op, const FieldValue& value) const { diff --git a/firestore/src/main/query_main.h b/firestore/src/main/query_main.h index 4f3e89016d..958df81f03 100644 --- a/firestore/src/main/query_main.h +++ b/firestore/src/main/query_main.h @@ -57,6 +57,8 @@ class QueryInternal { virtual Future Get(Source source); + AggregateQuery Count(); + ListenerRegistration AddSnapshotListener( MetadataChanges metadata_changes, EventListener* listener); diff --git a/release_build_files/readme.md b/release_build_files/readme.md index c5eb47fd8e..b0ab9d13c0 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -642,6 +642,11 @@ workflow use only during the development of your app, not for publicly shipping code. ## Release Notes +### Upcoming Release +- Changes + - Firestore: Added `Query::Count()`, which fetches the number of documents in the result + set without actually downloading the documents ([#1174](https://github.com/firebase/firebase-cpp-sdk/pull/1174)). + ### 10.4.0 - Changes - General (Android): Update to Firebase Android BoM version 31.2.0.