diff --git a/firestore/CMakeLists.txt b/firestore/CMakeLists.txt index 0cea8d1435..6142de43dd 100644 --- a/firestore/CMakeLists.txt +++ b/firestore/CMakeLists.txt @@ -41,6 +41,7 @@ set(common_SRCS src/common/hard_assert_common.h src/common/listener_registration.cc src/common/load_bundle_task_progress.cc + src/common/local_cache_settings.cc src/common/macros.h src/common/query.cc src/common/query_snapshot.cc diff --git a/firestore/integration_test_internal/src/bundle_test.cc b/firestore/integration_test_internal/src/bundle_test.cc index 3ac9760a9f..486a5e3058 100644 --- a/firestore/integration_test_internal/src/bundle_test.cc +++ b/firestore/integration_test_internal/src/bundle_test.cc @@ -19,6 +19,7 @@ #include <vector> #include "firebase/firestore.h" +#include "firebase/firestore/local_cache_settings.h" #include "firebase_test_framework.h" #include "firestore_integration_test.h" #include "gmock/gmock.h" @@ -327,7 +328,7 @@ TEST_F(BundleTest, LoadedDocumentsShouldNotBeGarbageCollectedRightAway) { // This test really only makes sense with memory persistence, as disk // persistence only ever lazily deletes data. auto new_settings = db->settings(); - new_settings.set_persistence_enabled(false); + new_settings.set_local_cache_settings(MemoryCacheSettings::Create()); db->set_settings(new_settings); auto bundle = CreateTestBundle(db); diff --git a/firestore/integration_test_internal/src/firestore_test.cc b/firestore/integration_test_internal/src/firestore_test.cc index 70d5974ef8..c10bb2a314 100644 --- a/firestore/integration_test_internal/src/firestore_test.cc +++ b/firestore/integration_test_internal/src/firestore_test.cc @@ -20,6 +20,11 @@ #include <future> #include <memory> #include <stdexcept> +#include "firebase/firestore/document_snapshot.h" +#include "firebase/firestore/field_value.h" +#include "firebase/firestore/local_cache_settings.h" +#include "firebase/firestore/map_field_value.h" +#include "firebase/firestore/source.h" #if defined(__ANDROID__) #include "android/firestore_integration_test_android.h" @@ -42,6 +47,7 @@ #include "util/future_test_util.h" #if !defined(__ANDROID__) #include "Firestore/core/src/util/autoid.h" +#include "Firestore/core/src/util/warnings.h" #include "firestore/src/main/converter_main.h" #include "firestore/src/main/firestore_main.h" #else @@ -1757,6 +1763,119 @@ TEST_F(FirestoreTest, ClearPersistenceWhileRunningFails) { EXPECT_EQ(await_clear_persistence.error(), Error::kErrorFailedPrecondition); } +class FirestoreCacheConfigTest : public FirestoreIntegrationTest { + protected: + void VerifyCachedDocumentDeletedImmediately(Firestore* db) { + Await(db->Document("rooms/eros") + .Set(MapFieldValue{{"desc", FieldValue::String("eros")}})); + + auto get_future = db->Document("rooms/eros").Get(Source::kCache); + const DocumentSnapshot* snapshot = Await(get_future); + ASSERT_NE(snapshot, nullptr); + ASSERT_FALSE(snapshot->is_valid()); + } + + void VerifyCachedDocumentStaysAround(Firestore* db) { + Await(db->Document("rooms/eros") + .Set(MapFieldValue{{"desc", FieldValue::String("eros")}})); + + auto get_future = db->Document("rooms/eros").Get(Source::kCache); + const DocumentSnapshot* snapshot = Await(get_future); + ASSERT_NE(snapshot, nullptr); + ASSERT_TRUE(snapshot->is_valid()); + ASSERT_THAT( + snapshot->GetData(), + ContainerEq(MapFieldValue{{"desc", FieldValue::String("eros")}})); + } +}; + +TEST_F(FirestoreCacheConfigTest, LegacyCacheConfigForMemoryCacheWorks) { + auto* db = TestFirestore("legacy_memory_cache"); + auto settings = db->settings(); + SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN(); + settings.set_persistence_enabled(false); + SUPPRESS_END(); + db->set_settings(std::move(settings)); + + VerifyCachedDocumentDeletedImmediately(db); +} + +TEST_F(FirestoreCacheConfigTest, LegacyCacheConfigForPersistenceCacheWorks) { + auto* db = TestFirestore("legacy_persistent_cache"); + auto settings = db->settings(); + SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN(); + settings.set_persistence_enabled(true); + SUPPRESS_END(); + db->set_settings(std::move(settings)); + + VerifyCachedDocumentStaysAround(db); +} + +TEST_F(FirestoreCacheConfigTest, NewCacheConfigForMemoryCacheWorks) { + auto* db = TestFirestore("new_memory_cache"); + auto settings = db->settings(); + settings.set_local_cache_settings(MemoryCacheSettings::Create()); + db->set_settings(std::move(settings)); + + VerifyCachedDocumentDeletedImmediately(db); +} + +TEST_F(FirestoreCacheConfigTest, NewCacheConfigForPersistenceCacheWorks) { + auto* db = TestFirestore("new_persistent_cache"); + auto settings = db->settings(); + settings.set_local_cache_settings( + PersistentCacheSettings::Create().WithSizeBytes(50 * 1024 * 1024)); + db->set_settings(std::move(settings)); + + VerifyCachedDocumentStaysAround(db); +} + +TEST_F(FirestoreCacheConfigTest, CannotMixNewAndLegacyCacheConfig) { + { + auto* db = TestFirestore("mixing_1"); + auto settings = db->settings(); + settings.set_local_cache_settings( + PersistentCacheSettings::Create().WithSizeBytes(50 * 1024 * 1024)); + + SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN(); + EXPECT_THROW(settings.set_cache_size_bytes(0), std::logic_error); + SUPPRESS_END(); + } + + { + auto* db = TestFirestore("mixing_2"); + auto settings = db->settings(); + SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN(); + settings.set_persistence_enabled(false); + SUPPRESS_END(); + EXPECT_THROW( + settings.set_local_cache_settings(MemoryCacheSettings::Create()), + std::logic_error); + } +} + +TEST_F(FirestoreCacheConfigTest, CanGetDocumentFromCacheWithMemoryLruGC) { + auto* db = TestFirestore("new_persistent_cache"); + auto settings = db->settings(); + settings.set_local_cache_settings( + MemoryCacheSettings::Create().WithGarbageCollectorSettings( + MemoryLruGCSettings::Create())); + db->set_settings(std::move(settings)); + + VerifyCachedDocumentStaysAround(db); +} + +TEST_F(FirestoreCacheConfigTest, CannotGetDocumentFromCacheFromMemoryEagerGC) { + auto* db = TestFirestore("new_persistent_cache"); + auto settings = db->settings(); + settings.set_local_cache_settings( + MemoryCacheSettings::Create().WithGarbageCollectorSettings( + MemoryEagerGCSettings::Create())); + db->set_settings(std::move(settings)); + + VerifyCachedDocumentDeletedImmediately(db); +} + // Note: this test only exists in C++. TEST_F(FirestoreTest, DomainObjectsReferToSameFirestoreInstance) { EXPECT_EQ(TestFirestore(), TestFirestore()->Document("foo/bar").firestore()); diff --git a/firestore/integration_test_internal/src/settings_test.cc b/firestore/integration_test_internal/src/settings_test.cc index ca62e2a74e..19e52269ca 100644 --- a/firestore/integration_test_internal/src/settings_test.cc +++ b/firestore/integration_test_internal/src/settings_test.cc @@ -16,7 +16,10 @@ #include <stdint.h> +#include "Firestore/core/src/util/warnings.h" #include "firebase/firestore.h" +#include "firebase/firestore/local_cache_settings.h" +#include "firebase_test_framework.h" #include "gtest/gtest.h" @@ -31,39 +34,51 @@ TEST(SettingsTest, Equality) { Settings settings1; settings1.set_host("foo"); settings1.set_ssl_enabled(true); + SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN(); settings1.set_persistence_enabled(true); settings1.set_cache_size_bytes(kFiveMb); + SUPPRESS_END(); Settings settings2; settings2.set_host("bar"); settings2.set_ssl_enabled(true); + SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN(); settings2.set_persistence_enabled(true); settings2.set_cache_size_bytes(kFiveMb); + SUPPRESS_END(); Settings settings3; settings3.set_host("foo"); settings3.set_ssl_enabled(false); + SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN(); settings3.set_persistence_enabled(true); settings3.set_cache_size_bytes(kFiveMb); + SUPPRESS_END(); Settings settings4; settings4.set_host("foo"); settings4.set_ssl_enabled(true); + SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN(); settings4.set_persistence_enabled(false); settings4.set_cache_size_bytes(kFiveMb); + SUPPRESS_END(); Settings settings5; settings5.set_host("foo"); settings5.set_ssl_enabled(true); + SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN(); settings5.set_persistence_enabled(true); settings5.set_cache_size_bytes(kSixMb); + SUPPRESS_END(); // This is the same as settings4. Settings settings6; settings6.set_host("foo"); settings6.set_ssl_enabled(true); + SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN(); settings6.set_persistence_enabled(false); settings6.set_cache_size_bytes(kFiveMb); + SUPPRESS_END(); EXPECT_TRUE(settings1 == settings1); EXPECT_TRUE(settings6 == settings4); @@ -94,6 +109,103 @@ TEST(SettingsTest, Equality) { EXPECT_TRUE(settings4 != settings5); } +TEST(SettingsTest, EqualityWithLocalCacheSettings) { + constexpr int64_t kFiveMb = 5 * 1024 * 1024; + constexpr int64_t kSixMb = 6 * 1024 * 1024; + + Settings settings1; + settings1.set_host("foo"); + settings1.set_ssl_enabled(true); + settings1.set_local_cache_settings( + PersistentCacheSettings::Create().WithSizeBytes(kFiveMb)); + + Settings settings2; + settings2.set_host("bar"); + settings2.set_ssl_enabled(true); + settings2.set_local_cache_settings( + PersistentCacheSettings::Create().WithSizeBytes(kFiveMb)); + + Settings settings3; + settings3.set_host("foo"); + settings3.set_ssl_enabled(false); + settings3.set_local_cache_settings( + PersistentCacheSettings::Create().WithSizeBytes(kFiveMb)); + + Settings settings4; + settings4.set_host("foo"); + settings4.set_ssl_enabled(true); + settings4.set_local_cache_settings(MemoryCacheSettings::Create()); + + Settings settings5; + settings5.set_host("foo"); + settings5.set_ssl_enabled(true); + settings5.set_local_cache_settings( + PersistentCacheSettings::Create().WithSizeBytes(kSixMb)); + + Settings settings6; + settings6.set_host("foo"); + settings6.set_ssl_enabled(true); + settings6.set_local_cache_settings( + MemoryCacheSettings::Create().WithGarbageCollectorSettings( + MemoryEagerGCSettings::Create())); + + Settings settings7; + settings7.set_host("foo"); + settings7.set_ssl_enabled(true); + settings7.set_local_cache_settings( + MemoryCacheSettings::Create().WithGarbageCollectorSettings( + MemoryLruGCSettings::Create().WithSizeBytes(kFiveMb))); + + Settings settings8; + settings8.set_host("foo"); + settings8.set_ssl_enabled(true); + settings8.set_local_cache_settings( + MemoryCacheSettings::Create().WithGarbageCollectorSettings( + MemoryLruGCSettings::Create().WithSizeBytes(kSixMb))); + + // Same as settings7 + Settings settings9; + settings9.set_host("foo"); + settings9.set_ssl_enabled(true); + settings9.set_local_cache_settings( + MemoryCacheSettings::Create().WithGarbageCollectorSettings( + MemoryLruGCSettings::Create().WithSizeBytes(kFiveMb))); + + EXPECT_TRUE(settings1 == settings1); + EXPECT_TRUE(settings6 == settings4); + EXPECT_TRUE(settings7 == settings9); + + EXPECT_FALSE(settings1 == settings2); + EXPECT_FALSE(settings1 == settings3); + EXPECT_FALSE(settings1 == settings4); + EXPECT_FALSE(settings1 == settings5); + EXPECT_FALSE(settings2 == settings3); + EXPECT_FALSE(settings2 == settings4); + EXPECT_FALSE(settings2 == settings5); + EXPECT_FALSE(settings3 == settings4); + EXPECT_FALSE(settings3 == settings5); + EXPECT_FALSE(settings4 == settings5); + EXPECT_FALSE(settings6 == settings7); + EXPECT_FALSE(settings7 == settings8); + + EXPECT_FALSE(settings1 != settings1); + EXPECT_FALSE(settings6 != settings4); + EXPECT_FALSE(settings7 != settings9); + + EXPECT_TRUE(settings1 != settings2); + EXPECT_TRUE(settings1 != settings3); + EXPECT_TRUE(settings1 != settings4); + EXPECT_TRUE(settings1 != settings5); + EXPECT_TRUE(settings2 != settings3); + EXPECT_TRUE(settings2 != settings4); + EXPECT_TRUE(settings2 != settings5); + EXPECT_TRUE(settings3 != settings4); + EXPECT_TRUE(settings3 != settings5); + EXPECT_TRUE(settings4 != settings5); + EXPECT_TRUE(settings6 != settings7); + EXPECT_TRUE(settings7 != settings8); +} + } // namespace } // namespace firestore } // namespace firebase diff --git a/firestore/src/common/local_cache_settings.cc b/firestore/src/common/local_cache_settings.cc new file mode 100644 index 0000000000..b5ea995ed1 --- /dev/null +++ b/firestore/src/common/local_cache_settings.cc @@ -0,0 +1,138 @@ +/* + * 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/local_cache_settings.h" +#include <memory> + +#include "firestore/src/common/hard_assert_common.h" +#if defined(__ANDROID__) +#else +#include "firestore/src/main/local_cache_settings_main.h" +#endif // defined(__ANDROID__) + +namespace firebase { +namespace firestore { + +PersistentCacheSettings PersistentCacheSettings::Create() { return {}; } + +PersistentCacheSettings::PersistentCacheSettings() { + settings_internal_ = std::make_shared<PersistentCacheSettingsInternal>(); +} + +PersistentCacheSettings PersistentCacheSettings::WithSizeBytes( + int64_t size) const { + PersistentCacheSettings new_settings; + new_settings.settings_internal_ = + std::make_shared<PersistentCacheSettingsInternal>( + this->settings_internal_->WithSizeBytes(size)); + return new_settings; +} + +int64_t PersistentCacheSettings::size_bytes() const { + return settings_internal_->size_bytes(); +} + +const LocalCacheSettingsInternal& PersistentCacheSettings::internal() const { + return *settings_internal_; +} + +MemoryEagerGCSettings MemoryEagerGCSettings::Create() { return {}; } + +MemoryEagerGCSettings::MemoryEagerGCSettings() { + settings_internal_ = std::make_shared<MemoryEagerGCSettingsInternal>(); +} + +const MemoryGarbageCollectorSettingsInternal& MemoryEagerGCSettings::internal() + const { + return *settings_internal_; +} + +MemoryLruGCSettings MemoryLruGCSettings::Create() { return {}; } + +MemoryLruGCSettings::MemoryLruGCSettings() { + settings_internal_ = std::make_shared<MemoryLruGCSettingsInternal>(); +} + +MemoryLruGCSettings::MemoryLruGCSettings( + const MemoryLruGCSettingsInternal& other) { + settings_internal_ = std::make_shared<MemoryLruGCSettingsInternal>(other); +} + +MemoryLruGCSettings MemoryLruGCSettings::WithSizeBytes(int64_t size) { + MemoryLruGCSettings result; + result.settings_internal_ = std::make_shared<MemoryLruGCSettingsInternal>( + this->settings_internal_->WithSizeBytes(size)); + return result; +} + +int64_t MemoryLruGCSettings::size_bytes() const { + return settings_internal_->size_bytes(); +} + +const MemoryGarbageCollectorSettingsInternal& MemoryLruGCSettings::internal() + const { + return *settings_internal_; +} + +MemoryCacheSettings MemoryCacheSettings::Create() { return {}; } + +MemoryCacheSettings::MemoryCacheSettings() { + settings_internal_ = std::make_shared<MemoryCacheSettingsInternal>(); +} + +MemoryCacheSettings MemoryCacheSettings::WithGarbageCollectorSettings( + const MemoryGarbageCollectorSettings& settings) const { + MemoryCacheSettings result; + result.settings_internal_ = std::make_shared<MemoryCacheSettingsInternal>( + this->settings_internal_->WithGarbageCollectorSettings(settings)); + return result; +} + +const LocalCacheSettingsInternal& MemoryCacheSettings::internal() const { + return *settings_internal_; +} + +bool operator==(const LocalCacheSettings& lhs, const LocalCacheSettings& rhs) { + return lhs.kind() == rhs.kind() && lhs.internal() == rhs.internal(); +} + +bool operator==(const MemoryCacheSettings& lhs, + const MemoryCacheSettings& rhs) { + return &lhs == &rhs || (*lhs.settings_internal_ == *rhs.settings_internal_); +} + +bool operator==(const PersistentCacheSettings& lhs, + const PersistentCacheSettings& rhs) { + return &lhs == &rhs || (*lhs.settings_internal_ == *rhs.settings_internal_); +} + +bool operator==(const MemoryGarbageCollectorSettings& lhs, + const MemoryGarbageCollectorSettings& rhs) { + return lhs.internal() == rhs.internal(); +} + +bool operator==(const MemoryEagerGCSettings& lhs, + const MemoryEagerGCSettings& rhs) { + return &lhs == &rhs || (*lhs.settings_internal_ == *rhs.settings_internal_); +} + +bool operator==(const MemoryLruGCSettings& lhs, + const MemoryLruGCSettings& rhs) { + return &lhs == &rhs || (*lhs.settings_internal_ == *rhs.settings_internal_); +} + +} // namespace firestore +} // namespace firebase diff --git a/firestore/src/common/settings.cc b/firestore/src/common/settings.cc index 5f2eb56a26..3be7d0c9a1 100644 --- a/firestore/src/common/settings.cc +++ b/firestore/src/common/settings.cc @@ -16,11 +16,14 @@ #include "firestore/src/include/firebase/firestore/settings.h" +#include <memory> #include <ostream> #include <sstream> - #include <utility> +#include "firebase/firestore/local_cache_settings.h" +#include "firestore/src/common/exception_common.h" + #if !defined(__ANDROID__) #include "Firestore/core/src/util/executor.h" #endif @@ -44,18 +47,66 @@ std::string ToStr(int64_t v) { } // namespace #if !defined(__APPLE__) -Settings::Settings() : host_(kDefaultHost) {} +Settings::Settings() + : host_(kDefaultHost), + local_cache_settings_(std::make_shared<PersistentCacheSettings>( + PersistentCacheSettings::Create())) {} #endif void Settings::set_host(std::string host) { host_ = std::move(host); } void Settings::set_ssl_enabled(bool enabled) { ssl_enabled_ = enabled; } +const LocalCacheSettings& Settings::local_cache_settings() { + if (cache_settings_source_ == CacheSettingsSource::kOld) { + if (is_persistence_enabled()) { + local_cache_settings_ = std::make_shared<PersistentCacheSettings>( + PersistentCacheSettings::Create().WithSizeBytes(cache_size_bytes())); + } else { + local_cache_settings_ = + std::make_shared<MemoryCacheSettings>(MemoryCacheSettings::Create()); + } + } + + return *local_cache_settings_; +} + +void Settings::set_local_cache_settings(const LocalCacheSettings& cache) { + if (cache_settings_source_ == CacheSettingsSource::kOld) { + SimpleThrowIllegalState( + "Cannot mix set_local_cache_settings() with legacy cache api like " + "set_persistence_enabled() or set_cache_size_bytes()"); + } + + cache_settings_source_ = CacheSettingsSource::kNew; + if (cache.kind() == LocalCacheSettings::Kind::kPersistent) { + local_cache_settings_ = std::make_shared<PersistentCacheSettings>( + static_cast<const PersistentCacheSettings&>(cache)); + } else { + local_cache_settings_ = std::make_shared<MemoryCacheSettings>( + static_cast<const MemoryCacheSettings&>(cache)); + } +} + void Settings::set_persistence_enabled(bool enabled) { + if (cache_settings_source_ == CacheSettingsSource::kNew) { + SimpleThrowIllegalState( + "Cannot mix legacy cache api set_persistence_enabled() with new cache " + "api set_local_cache_settings()"); + } + + cache_settings_source_ = CacheSettingsSource::kOld; persistence_enabled_ = enabled; } void Settings::set_cache_size_bytes(int64_t value) { + if (cache_settings_source_ == CacheSettingsSource::kNew) { + SimpleThrowIllegalState( + "Cannot mix legacy cache api set_cache_size_bytes() with new cache api " + "set_local_cache_settings()"); + } + + cache_settings_source_ = CacheSettingsSource::kOld; cache_size_bytes_ = value; } @@ -70,6 +121,40 @@ std::ostream& operator<<(std::ostream& out, const Settings& settings) { return out << settings.ToString(); } +bool operator==(const Settings& lhs, const Settings& rhs) { + if (lhs.host() != rhs.host()) { + return false; + } + + if (lhs.is_ssl_enabled() != rhs.is_ssl_enabled()) { + return false; + } + + if (lhs.cache_settings_source_ != rhs.cache_settings_source_) { + return false; + } + + if (*lhs.local_cache_settings_ != *rhs.local_cache_settings_) { + return false; + } + + if (lhs.is_persistence_enabled() != rhs.is_persistence_enabled()) { + return false; + } + + if (lhs.cache_size_bytes() != rhs.cache_size_bytes()) { + return false; + } + +#if defined(__OBJC__) + if (lhs.dispatch_queue() != rhs.dispatch_queue()) { + return false; + } +#endif // defined(__OBJC__) + + return true; +} + // Apple uses a different mechanism, defined in `settings_apple.mm`. #if !defined(__APPLE__) && !defined(__ANDROID__) std::unique_ptr<util::Executor> Settings::CreateExecutor() const { diff --git a/firestore/src/common/settings_apple.mm b/firestore/src/common/settings_apple.mm index 5ff4b325df..eba499a857 100644 --- a/firestore/src/common/settings_apple.mm +++ b/firestore/src/common/settings_apple.mm @@ -20,6 +20,7 @@ #include <memory> #include "Firestore/core/src/util/executor_libdispatch.h" +#include "firebase/firestore/local_cache_settings.h" namespace firebase { namespace firestore { @@ -36,7 +37,9 @@ Settings::Settings() : host_(kDefaultHost), executor_( - Executor::CreateSerial("com.google.firebase.firestore.callback")) {} + Executor::CreateSerial("com.google.firebase.firestore.callback")), + local_cache_settings_(std::make_unique<PersistentCacheSettings>( + PersistentCacheSettings::Create())) {} std::unique_ptr<Executor> Settings::CreateExecutor() const { return std::make_unique<ExecutorLibdispatch>(dispatch_queue()); diff --git a/firestore/src/include/firebase/firestore/local_cache_settings.h b/firestore/src/include/firebase/firestore/local_cache_settings.h new file mode 100644 index 0000000000..b7d4a5eaa8 --- /dev/null +++ b/firestore/src/include/firebase/firestore/local_cache_settings.h @@ -0,0 +1,313 @@ +/* + * 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_LOCAL_CACHE_SETTINGS_H_ +#define FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_LOCAL_CACHE_SETTINGS_H_ + +#include <cstdint> +#include <memory> + +#include "firestore/src/include/firebase/firestore/settings.h" + +namespace firebase { +namespace firestore { + +class LocalCacheSettingsInternal; +class PersistentCacheSettingsInternal; +class MemoryCacheSettingsInternal; +class MemoryLruGCSettingsInternal; +class MemoryEagerGCSettingsInternal; +class MemoryGarbageCollectorSettingsInternal; + +/** + * Abstract class implemented by all supported cache settings. + * + * `PersistentCacheSettings` and `MemoryCacheSettings` are the only cache types + * supported by the SDK. Custom implementation is not supported. + */ +class LocalCacheSettings { + public: + enum class Kind { kMemory, kPersistent }; + + virtual ~LocalCacheSettings() = default; + + virtual Kind kind() const = 0; + + /** Equality function. */ + friend bool operator==(const LocalCacheSettings& lhs, + const LocalCacheSettings& rhs); + + private: + friend class FirestoreInternal; + friend class Settings; + friend class PersistentCacheSettings; + friend class MemoryCacheSettings; + + LocalCacheSettings() = default; + + virtual const LocalCacheSettingsInternal& internal() const = 0; +}; + +/** + * Configures the SDK to use a persistent cache. Firestore documents and + * mutations are persisted across App restart. + * + * This is the default cache type unless explicitly specified otherwise. + * + * To use, create an instance using `PersistentCacheSettings::Create`, then + * pass it to an instance of `Settings` via `set_local_cache_settings()`, and + * use the `Settings` instance to configure the Firestore SDK. + */ +class PersistentCacheSettings final : public LocalCacheSettings { + public: + /** Create a default instance `PersistenceCacheSettings`. */ + static PersistentCacheSettings Create(); + + /** Equality function. */ + friend bool operator==(const PersistentCacheSettings& lhs, + const PersistentCacheSettings& rhs); + + /** + * Copies this settings instance, with the approximate cache size threshold + * for the on-disk data set to the given number in term of number of bytes, + * and return the new setting instance. + * + * If the cache grows beyond this size, Firestore SDK will start removing data + * that hasn't been recently used. The SDK does not guarantee that the cache + * will stay below that size, only that if the cache exceeds the given size, + * cleanup will be attempted. + * + * By default, persistence cache is enabled with a cache size of 100 MB. The + * minimum value is 1 MB (1 * 1024 * 1024 bytes). + */ + PersistentCacheSettings WithSizeBytes(int64_t size) const; + + /** + * Returns the approximate cache size threshold configured. Garbage collection + * kicks in once the cache size exceeds this threshold. + */ + int64_t size_bytes() const; + + private: + friend class Settings; + friend class FirestoreInternal; + + PersistentCacheSettings(); + + LocalCacheSettings::Kind kind() const override { + return LocalCacheSettings::Kind::kPersistent; + } + + // Get the corresponding settings object from the core sdk. + const LocalCacheSettingsInternal& internal() const override; + + std::shared_ptr<PersistentCacheSettingsInternal> settings_internal_; +}; + +class MemoryGarbageCollectorSettings; + +/** + * Configures the SDK to use a memory cache. Firestore documents and mutations + * are NOT persisted across App restart. + * + * To use, create an instance using `MemoryCacheSettings::Create`, then + * pass it to an instance of `Settings` via `set_local_cache_settings()`, and + * use the `Settings` instance to configure the Firestore SDK. + */ +class MemoryCacheSettings final : public LocalCacheSettings { + public: + /** Create a default instance `MemoryCacheSettings`. */ + static MemoryCacheSettings Create(); + + /** Equality function. */ + friend bool operator==(const MemoryCacheSettings& lhs, + const MemoryCacheSettings& rhs); + + /** + * Copies this settings instance, with its `MemoryGarbageCollectorSettins` set + * the the given parameter, and returns the new settings instance. + */ + MemoryCacheSettings WithGarbageCollectorSettings( + const MemoryGarbageCollectorSettings& settings) const; + + private: + friend class Settings; + friend class FirestoreInternal; + + MemoryCacheSettings(); + + LocalCacheSettings::Kind kind() const override { + return LocalCacheSettings::Kind::kMemory; + } + + const LocalCacheSettingsInternal& internal() const override; + + std::shared_ptr<MemoryCacheSettingsInternal> settings_internal_; +}; + +/** + * Abstract class implemented by all supported memory garbage collector. + * + * `MemoryEagerGCSettings` and `MemoryLruGCSettings` are the only memory + * garbage collectors supported by the SDK. Custom implementation is not + * supported. + */ +class MemoryGarbageCollectorSettings { + public: + virtual ~MemoryGarbageCollectorSettings() = default; + /** Equality function. */ + friend bool operator==(const MemoryGarbageCollectorSettings& lhs, + const MemoryGarbageCollectorSettings& rhs); + + private: + friend class MemoryCacheSettings; + friend class MemoryEagerGCSettings; + friend class MemoryLruGCSettings; + friend class MemoryCacheSettingsInternal; + + MemoryGarbageCollectorSettings() = default; + + virtual const MemoryGarbageCollectorSettingsInternal& internal() const = 0; +}; + +/** + * Configures the memory cache to use a garbage collector with an eager + * strategy. + * + * An eager garbage collector deletes documents whenever they are not part of + * any active queries, and have no local mutations attached to them. + * + * This collector tries to ensure lowest memory footprints from the SDK, + * at the risk of documents not being cached for offline queries or for + * direct queries to the cache. + * + * To use, pass an instance of `MemoryEagerGCSettings` to + * `MemoryCacheSettings::WithGarbageCollectorSettings()` to get a new instance + * of `MemoryCacheSettings`, which can be used to configure the SDK. + */ +class MemoryEagerGCSettings final : public MemoryGarbageCollectorSettings { + public: + /** Create a default instance `MemoryEagerGCSettings`. */ + static MemoryEagerGCSettings Create(); + + /** Equality function. */ + friend bool operator==(const MemoryEagerGCSettings& lhs, + const MemoryEagerGCSettings& rhs); + + private: + friend class MemoryCacheSettings; + MemoryEagerGCSettings(); + const MemoryGarbageCollectorSettingsInternal& internal() const override; + + std::shared_ptr<MemoryEagerGCSettingsInternal> settings_internal_; +}; + +/** + * Configures the memory cache to use a garbage collector with an + * least-recently-used strategy. + * + * A LRU garbage collector deletes Least-Recently-Used documents in multiple + * batches. + * + * This collector is configured with a target size, and will only perform + * collection when the cached documents exceed the target size. It avoids + * querying backend repeated for the same query or document, at the risk + * of having a larger memory footprint. + * + * To use, pass an instance of `MemoryLRUGCSettings` to + * `MemoryCacheSettings::WithGarbageCollectorSettings()` to get a new instance + * of `MemoryCacheSettings`, which can be used to configure the SDK. + */ +class MemoryLruGCSettings final : public MemoryGarbageCollectorSettings { + public: + /** Create a default instance `MemoryLruGCSettings`. */ + static MemoryLruGCSettings Create(); + + /** Equality function. */ + friend bool operator==(const MemoryLruGCSettings& lhs, + const MemoryLruGCSettings& rhs); + + /** + * Copies this settings instance, with the approximate cache size threshold + * for the memory data set to the given number in term of number of bytes, and + * return the new setting instance. + * + * If the cache grows beyond this size, Firestore SDK will start removing data + * that hasn't been recently used. The SDK does not guarantee that the cache + * will stay below that size, only that if the cache exceeds the given size, + * cleanup will be attempted. + * + * By default, memory LRU cache is enabled with a cache size of 100 MB. The + * minimum value is 1 MB (1 * 1024 * 1024 bytes). + */ + MemoryLruGCSettings WithSizeBytes(int64_t size); + + /** + * Returns the approximate cache size threshold configured. Garbage collection + * kicks in once the cache size exceeds this threshold. + */ + int64_t size_bytes() const; + + private: + friend class MemoryCacheSettings; + MemoryLruGCSettings(); + MemoryLruGCSettings(const MemoryLruGCSettingsInternal& other); + + const MemoryGarbageCollectorSettingsInternal& internal() const override; + + std::shared_ptr<MemoryLruGCSettingsInternal> settings_internal_; +}; + +/** Inequality function. */ +inline bool operator!=(const LocalCacheSettings& lhs, + const LocalCacheSettings& rhs) { + return !(lhs == rhs); +} + +/** Inequality function. */ +inline bool operator!=(const MemoryCacheSettings& lhs, + const MemoryCacheSettings& rhs) { + return !(lhs == rhs); +} + +/** Inequality function. */ +inline bool operator!=(const PersistentCacheSettings& lhs, + const PersistentCacheSettings& rhs) { + return !(lhs == rhs); +} + +/** Inequality function. */ +inline bool operator!=(const MemoryGarbageCollectorSettings& lhs, + const MemoryGarbageCollectorSettings& rhs) { + return !(lhs == rhs); +} + +/** Inequality function. */ +inline bool operator!=(const MemoryEagerGCSettings& lhs, + const MemoryEagerGCSettings& rhs) { + return !(lhs == rhs); +} + +/** Inequality function. */ +inline bool operator!=(const MemoryLruGCSettings& lhs, + const MemoryLruGCSettings& rhs) { + return !(lhs == rhs); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_LOCAL_CACHE_SETTINGS_H_ diff --git a/firestore/src/include/firebase/firestore/settings.h b/firestore/src/include/firebase/firestore/settings.h index 8169d9f594..c83b2409d7 100644 --- a/firestore/src/include/firebase/firestore/settings.h +++ b/firestore/src/include/firebase/firestore/settings.h @@ -17,6 +17,7 @@ #ifndef FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_SETTINGS_H_ #define FIREBASE_FIRESTORE_SRC_INCLUDE_FIREBASE_FIRESTORE_SETTINGS_H_ +#include "firebase/internal/common.h" #if defined(__OBJC__) #include <dispatch/dispatch.h> #endif @@ -47,6 +48,7 @@ class Executor; #endif class FirestoreInternal; +class LocalCacheSettings; /** Settings used to configure a Firestore instance. */ class Settings final { @@ -137,13 +139,37 @@ class Settings final { void set_ssl_enabled(bool enabled); /** + * Returns a reference to the `LocalCacheSettings` instance + * used to configure this SDK. + */ + const LocalCacheSettings& local_cache_settings(); + + /** + * Configures the SDK with the given `LocalCacheSettings` instance. + * + * By default, persistence cache is enabled, with a cache size of 100 MB. + * + * See documentation of `PersistentCacheSettings` to under the default + * settings. + * + * @param cache_settings Settings object to configue this SDK. + */ + void set_local_cache_settings(const LocalCacheSettings& cache_settings); + + /** + * NOTE: This method is deprecated in favor of `set_local_cache_settings()`. + * It will be deleted in a future major release. + * * Enables or disables local persistent storage. * * @param enabled Set true to enable local persistent storage. */ - void set_persistence_enabled(bool enabled); + FIREBASE_DEPRECATED void set_persistence_enabled(bool enabled); /** + * NOTE: This method is deprecated in favor of `set_local_cache_settings()`. + * It will be deleted in a future major release. + * * Sets an approximate cache size threshold for the on-disk data. If the cache * grows beyond this size, Cloud Firestore will start removing data that * hasn't been recently used. The size is not a guarantee that the cache will @@ -153,7 +179,7 @@ class Settings final { * By default, collection is enabled with a cache size of 100 MB. The minimum * value is 1 MB. */ - void set_cache_size_bytes(int64_t value); + FIREBASE_DEPRECATED void set_cache_size_bytes(int64_t value); #if defined(__OBJC__) || defined(DOXYGEN) /** @@ -208,11 +234,19 @@ class Settings final { */ friend std::ostream& operator<<(std::ostream& out, const Settings& settings); + /** Checks `lhs` and `rhs` for equality. */ + friend bool operator==(const Settings& lhs, const Settings& rhs); + private: static constexpr int64_t kDefaultCacheSizeBytes = 100 * 1024 * 1024; + enum class CacheSettingsSource { kNone, kNew, kOld }; std::string host_; bool ssl_enabled_ = true; + + CacheSettingsSource cache_settings_source_{CacheSettingsSource::kNone}; + + std::shared_ptr<LocalCacheSettings> local_cache_settings_; bool persistence_enabled_ = true; int64_t cache_size_bytes_ = kDefaultCacheSizeBytes; @@ -228,14 +262,6 @@ class Settings final { #endif }; -/** Checks `lhs` and `rhs` for equality. */ -inline bool operator==(const Settings& lhs, const Settings& rhs) { - return lhs.host() == rhs.host() && - lhs.is_ssl_enabled() == rhs.is_ssl_enabled() && - lhs.is_persistence_enabled() == rhs.is_persistence_enabled() && - lhs.cache_size_bytes() == rhs.cache_size_bytes(); -} - /** Checks `lhs` and `rhs` for inequality. */ inline bool operator!=(const Settings& lhs, const Settings& rhs) { return !(lhs == rhs); diff --git a/firestore/src/main/firestore_main.cc b/firestore/src/main/firestore_main.cc index 8a512a49e5..d40469e9e6 100644 --- a/firestore/src/main/firestore_main.cc +++ b/firestore/src/main/firestore_main.cc @@ -23,6 +23,7 @@ #include "Firestore/core/src/api/document_reference.h" #include "Firestore/core/src/api/query_core.h" +#include "Firestore/core/src/api/settings.h" #include "Firestore/core/src/credentials/empty_credentials_provider.h" #include "Firestore/core/src/model/database_id.h" #include "Firestore/core/src/model/resource_path.h" @@ -32,16 +33,19 @@ #include "Firestore/core/src/util/executor.h" #include "Firestore/core/src/util/log.h" #include "Firestore/core/src/util/status.h" +#include "Firestore/core/src/util/warnings.h" #include "absl/memory/memory.h" #include "absl/types/any.h" #include "app/src/include/firebase/future.h" #include "app/src/reference_counted_future_impl.h" #include "firebase/firestore/firestore_version.h" +#include "firebase/firestore/settings.h" #include "firestore/src/common/exception_common.h" #include "firestore/src/common/hard_assert_common.h" #include "firestore/src/common/macros.h" #include "firestore/src/common/util.h" #include "firestore/src/include/firebase/firestore.h" +#include "firestore/src/include/firebase/firestore/local_cache_settings.h" #include "firestore/src/main/converter_main.h" #include "firestore/src/main/create_app_check_credentials_provider.h" #include "firestore/src/main/create_credentials_provider.h" @@ -49,6 +53,7 @@ #include "firestore/src/main/document_reference_main.h" #include "firestore/src/main/document_snapshot_main.h" #include "firestore/src/main/listener_main.h" +#include "firestore/src/main/local_cache_settings_main.h" namespace firebase { namespace firestore { @@ -193,8 +198,16 @@ Settings FirestoreInternal::settings() const { const api::Settings& from = firestore_core_->settings(); result.set_host(from.host()); result.set_ssl_enabled(from.ssl_enabled()); + + // TODO(wuandy): We use the deprecated API for default settings, but mark + // `cache_settings_source_` as `kNew` such that new settings API is not + // rejected by runtime checks. This should be removed when legacy API is + // removed. + SUPPRESS_DEPRECATED_DECLARATIONS_BEGIN() result.set_persistence_enabled(from.persistence_enabled()); result.set_cache_size_bytes(from.cache_size_bytes()); + SUPPRESS_END() + result.cache_settings_source_ = Settings::CacheSettingsSource::kNone; return result; } @@ -203,8 +216,16 @@ void FirestoreInternal::set_settings(Settings from) { api::Settings settings; settings.set_host(std::move(from.host())); settings.set_ssl_enabled(from.is_ssl_enabled()); - settings.set_persistence_enabled(from.is_persistence_enabled()); - settings.set_cache_size_bytes(from.cache_size_bytes()); + // TODO(wuandy): Checking `from.local_cache_settings_` is required, because + // FirestoreInternal::settings() overrides used_legacy_cache_settings_. All + // this special logic should go away when legacy cache config is removed. + if (from.cache_settings_source_ == Settings::CacheSettingsSource::kNew) { + settings.set_local_cache_settings( + from.local_cache_settings().internal().core_settings()); + } else { + settings.set_persistence_enabled(from.is_persistence_enabled()); + settings.set_cache_size_bytes(from.cache_size_bytes()); + } firestore_core_->set_settings(settings); std::unique_ptr<Executor> user_executor = from.CreateExecutor(); diff --git a/firestore/src/main/firestore_main.h b/firestore/src/main/firestore_main.h index 7920eccc4f..a9a2739b36 100644 --- a/firestore/src/main/firestore_main.h +++ b/firestore/src/main/firestore_main.h @@ -48,6 +48,7 @@ class Firestore; class ListenerRegistrationInternal; class Transaction; class WriteBatch; +class Settings; namespace util { class Executor; diff --git a/firestore/src/main/local_cache_settings_main.h b/firestore/src/main/local_cache_settings_main.h new file mode 100644 index 0000000000..7d23ad5068 --- /dev/null +++ b/firestore/src/main/local_cache_settings_main.h @@ -0,0 +1,177 @@ +/* + * 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_LOCAL_CACHE_SETTINGS_MAIN_H_ +#define FIREBASE_FIRESTORE_SRC_MAIN_LOCAL_CACHE_SETTINGS_MAIN_H_ + +#include <cstdint> +#include <utility> + +#include "Firestore/core/src/api/settings.h" +#include "firebase/firestore/local_cache_settings.h" + +namespace firebase { +namespace firestore { + +class LocalCacheSettingsInternal { + public: + friend bool operator==(const LocalCacheSettingsInternal& lhs, + const LocalCacheSettingsInternal& rhs) { + return &lhs == &rhs || lhs.core_settings() == rhs.core_settings(); + } + + virtual const api::LocalCacheSettings& core_settings() const = 0; +}; + +class PersistentCacheSettingsInternal final + : public LocalCacheSettingsInternal { + public: + explicit PersistentCacheSettingsInternal() = default; + + friend bool operator==(const PersistentCacheSettingsInternal& lhs, + const PersistentCacheSettingsInternal& rhs) { + return &lhs == &rhs || lhs.settings_ == rhs.settings_; + } + + PersistentCacheSettingsInternal WithSizeBytes(int64_t size) { + PersistentCacheSettingsInternal result; + result.set_core_settings(this->core_settings().WithSizeBytes(size)); + return result; + } + + int64_t size_bytes() const { return settings_.size_bytes(); } + + const api::PersistentCacheSettings& core_settings() const override { + return settings_; + } + void set_core_settings(const api::PersistentCacheSettings& settings) { + settings_ = settings; + } + + private: + api::PersistentCacheSettings settings_; +}; + +class MemoryGarbageCollectorSettingsInternal { + public: + friend bool operator==(const MemoryGarbageCollectorSettingsInternal& lhs, + const MemoryGarbageCollectorSettingsInternal& rhs) { + return &lhs == &rhs || lhs.core_settings() == rhs.core_settings(); + } + + virtual const api::MemoryGargabeCollectorSettings& core_settings() const = 0; +}; + +class MemoryEagerGCSettingsInternal final + : public MemoryGarbageCollectorSettingsInternal { + public: + explicit MemoryEagerGCSettingsInternal() = default; + + friend bool operator==(const MemoryEagerGCSettingsInternal& lhs, + const MemoryEagerGCSettingsInternal& rhs) { + return &lhs == &rhs || lhs.settings_ == rhs.settings_; + } + + const api::MemoryEagerGcSettings& core_settings() const override { + return settings_; + } + void set_core_settings(const api::MemoryEagerGcSettings& settings) { + settings_ = settings; + } + + private: + api::MemoryEagerGcSettings settings_; +}; + +class MemoryLruGCSettingsInternal final + : public MemoryGarbageCollectorSettingsInternal { + public: + explicit MemoryLruGCSettingsInternal() = default; + + friend bool operator==(const MemoryLruGCSettingsInternal& lhs, + const MemoryLruGCSettingsInternal& rhs) { + return &lhs == &rhs || lhs.settings_ == rhs.settings_; + } + + MemoryLruGCSettingsInternal WithSizeBytes(int64_t size) { + MemoryLruGCSettingsInternal result; + result.set_core_settings(this->core_settings().WithSizeBytes(size)); + return result; + } + + int64_t size_bytes() const { return settings_.size_bytes(); } + + const api::MemoryLruGcSettings& core_settings() const override { + return settings_; + } + void set_core_settings(const api::MemoryLruGcSettings& settings) { + settings_ = settings; + } + + private: + api::MemoryLruGcSettings settings_; +}; + +class MemoryCacheSettingsInternal final : public LocalCacheSettingsInternal { + public: + explicit MemoryCacheSettingsInternal() = default; + + friend bool operator==(const MemoryCacheSettingsInternal& lhs, + const MemoryCacheSettingsInternal& rhs) { + return &lhs == &rhs || lhs.settings_ == rhs.settings_; + } + + MemoryCacheSettingsInternal WithGarbageCollectorSettings( + const MemoryGarbageCollectorSettings& gc_settings) { + return MemoryCacheSettingsInternal( + api::MemoryCacheSettings().WithMemoryGarbageCollectorSettings( + gc_settings.internal().core_settings())); + } + + const api::MemoryCacheSettings& core_settings() const override { + return settings_; + } + + private: + explicit MemoryCacheSettingsInternal(const api::MemoryCacheSettings& settings) + : settings_(settings) {} + api::MemoryCacheSettings settings_; +}; + +inline bool operator!=(const MemoryCacheSettingsInternal& lhs, + const MemoryCacheSettingsInternal& rhs) { + return !(lhs == rhs); +} + +inline bool operator!=(const MemoryLruGCSettingsInternal& lhs, + const MemoryLruGCSettingsInternal& rhs) { + return !(lhs == rhs); +} + +inline bool operator!=(const MemoryEagerGCSettingsInternal& lhs, + const MemoryEagerGCSettingsInternal& rhs) { + return !(lhs == rhs); +} + +inline bool operator!=(const PersistentCacheSettingsInternal& lhs, + const PersistentCacheSettingsInternal& rhs) { + return !(lhs == rhs); +} + +} // namespace firestore +} // namespace firebase + +#endif // FIREBASE_FIRESTORE_SRC_MAIN_LOCAL_CACHE_SETTINGS_MAIN_H_