|  | // Copyright 2020 The Chromium Authors | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #ifndef COMPONENTS_SESSION_PROTO_DB_SESSION_PROTO_DB_H_ | 
|  | #define COMPONENTS_SESSION_PROTO_DB_SESSION_PROTO_DB_H_ | 
|  |  | 
|  | #include <memory> | 
|  | #include <optional> | 
|  | #include <string> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/containers/contains.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/functional/callback_helpers.h" | 
|  | #include "base/memory/raw_ptr.h" | 
|  | #include "base/memory/weak_ptr.h" | 
|  | #include "base/metrics/histogram_functions.h" | 
|  | #include "base/strings/string_util.h" | 
|  | #include "base/task/sequenced_task_runner.h" | 
|  | #include "base/task/single_thread_task_runner.h" | 
|  | #include "base/task/thread_pool.h" | 
|  | #include "components/commerce/core/proto/persisted_state_db_content.pb.h" | 
|  | #include "components/keyed_service/core/keyed_service.h" | 
|  | #include "components/leveldb_proto/public/proto_database.h" | 
|  | #include "components/leveldb_proto/public/proto_database_provider.h" | 
|  | #include "components/session_proto_db/session_proto_storage.h" | 
|  | #include "third_party/leveldatabase/src/include/leveldb/options.h" | 
|  |  | 
|  | namespace { | 
|  | const char kOrphanedDataCountHistogramName[] = | 
|  | "Tabs.PersistedTabData.Storage.LevelDB.OrphanedDataCount"; | 
|  | }  // namespace | 
|  |  | 
|  | class SessionProtoDBTest; | 
|  |  | 
|  | template <typename T> | 
|  | class SessionProtoDBFactory; | 
|  |  | 
|  | // General purpose per session (BrowserContext/BrowserState), per proto key -> | 
|  | // proto database where the template is the proto which is being stored. A | 
|  | // SessionProtoDB should be acquired using SessionProtoDBFactory. SessionProtoDB | 
|  | // is a wrapper on top of leveldb_proto which: | 
|  | // - Is specifically for databases which are per session | 
|  | // (BrowserContext/BrowserState) | 
|  | //   and per proto (leveldb_proto is a proto database which may or may not be | 
|  | //   per BrowserContext/BrowserState). | 
|  | // - Provides a simplified interface for the use cases that surround | 
|  | //   SessionProtoDB such as providing LoadContentWithPrefix instead of the | 
|  | //   more generic API in | 
|  | //   leveldb_proto which requires a filter to be passed in. | 
|  | // - Is a KeyedService to support the per session (BrowserContext/BrowserState) | 
|  | //   nature of the database. | 
|  | template <typename T> | 
|  | class SessionProtoDB : public KeyedService, public SessionProtoStorage<T> { | 
|  | public: | 
|  | using KeyAndValue = std::pair<std::string, T>; | 
|  |  | 
|  | // Callback which is used when content is acquired. | 
|  | using LoadCallback = base::OnceCallback<void(bool, std::vector<KeyAndValue>)>; | 
|  |  | 
|  | // Used for confirming an operation was completed successfully (e.g. | 
|  | // insert, delete). This will be invoked on a different SequenceRunner | 
|  | // to SessionProtoDB. | 
|  | using OperationCallback = base::OnceCallback<void(bool)>; | 
|  |  | 
|  | // Represents an entry in the database. | 
|  | using ContentEntry = typename leveldb_proto::ProtoDatabase<T>::KeyEntryVector; | 
|  |  | 
|  | // Initializes the database. | 
|  | SessionProtoDB( | 
|  | leveldb_proto::ProtoDatabaseProvider* proto_database_provider, | 
|  | const base::FilePath& database_dir, | 
|  | leveldb_proto::ProtoDbType proto_db_type, | 
|  | scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner); | 
|  |  | 
|  | SessionProtoDB(const SessionProtoDB&) = delete; | 
|  | SessionProtoDB& operator=(const SessionProtoDB&) = delete; | 
|  | ~SessionProtoDB() override; | 
|  |  | 
|  | // SessionProtoStorage implementation: | 
|  | void LoadOneEntry(const std::string& key, LoadCallback callback) override; | 
|  |  | 
|  | void LoadAllEntries(LoadCallback callback) override; | 
|  |  | 
|  | void LoadContentWithPrefix(const std::string& key_prefix, | 
|  | LoadCallback callback) override; | 
|  |  | 
|  | void PerformMaintenance(const std::vector<std::string>& keys_to_keep, | 
|  | const std::string& key_substring_to_match, | 
|  | OperationCallback callback) override; | 
|  |  | 
|  | void InsertContent(const std::string& key, | 
|  | const T& value, | 
|  | OperationCallback callback) override; | 
|  |  | 
|  | void DeleteOneEntry(const std::string& key, | 
|  | OperationCallback callback) override; | 
|  |  | 
|  | void UpdateEntries(std::unique_ptr<ContentEntry> entries_to_update, | 
|  | std::unique_ptr<std::vector<std::string>> keys_to_remove, | 
|  | OperationCallback callback) override; | 
|  |  | 
|  | void DeleteContentWithPrefix(const std::string& key_prefix, | 
|  | OperationCallback callback) override; | 
|  |  | 
|  | void DeleteAllContent(OperationCallback callback) override; | 
|  |  | 
|  | void Destroy() const override; | 
|  |  | 
|  | private: | 
|  | friend class ::SessionProtoDBTest; | 
|  | template <typename U> | 
|  | friend class ::SessionProtoDBFactory; | 
|  |  | 
|  | // Used for testing. | 
|  | SessionProtoDB( | 
|  | std::unique_ptr<leveldb_proto::ProtoDatabase<T>> storage_database, | 
|  | scoped_refptr<base::SequencedTaskRunner> task_runner, | 
|  | scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner); | 
|  |  | 
|  | // Passes back database status following database initialization. | 
|  | void OnDatabaseInitialized(leveldb_proto::Enums::InitStatus status); | 
|  |  | 
|  | // Callback when one entry is loaded. | 
|  | void OnLoadOneEntry(LoadCallback callback, | 
|  | bool success, | 
|  | std::unique_ptr<T> entry); | 
|  |  | 
|  | // Callback when content is loaded. | 
|  | void OnLoadContent(LoadCallback callback, | 
|  | bool success, | 
|  | std::unique_ptr<std::vector<T>> content); | 
|  |  | 
|  | // Callback when PerformMaintenance is complete. | 
|  | void OnPerformMaintenance(OperationCallback callback, | 
|  | bool success, | 
|  | std::unique_ptr<std::vector<T>> entries_to_delete); | 
|  |  | 
|  | // Callback when an operation (e.g. insert or delete) is called. | 
|  | void OnOperationCommitted(OperationCallback callback, bool success); | 
|  |  | 
|  | // Returns true if initialization status of database is not yet known. | 
|  | bool InitStatusUnknown() const; | 
|  |  | 
|  | // Returns true if the database failed to initialize. | 
|  | bool FailedToInit() const; | 
|  |  | 
|  | static bool DatabasePrefixFilter(const std::string& key_prefix, | 
|  | const std::string& key) { | 
|  | return base::StartsWith(key, key_prefix, base::CompareCase::SENSITIVE); | 
|  | } | 
|  |  | 
|  | // Status of the database initialization. | 
|  | std::optional<leveldb_proto::Enums::InitStatus> database_status_; | 
|  |  | 
|  | // The database for storing content storage information. | 
|  | std::unique_ptr<leveldb_proto::ProtoDatabase<T>> storage_database_; | 
|  |  | 
|  | // Store operations until the database is initialized at which point | 
|  | // |deferred_operations_| is flushed and all operations are executed. | 
|  | std::vector<base::OnceClosure> deferred_operations_; | 
|  |  | 
|  | // Task Runner for posting tasks to UI thread. | 
|  | scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner_; | 
|  |  | 
|  | base::WeakPtrFactory<SessionProtoDB> weak_ptr_factory_{this}; | 
|  | }; | 
|  |  | 
|  | template <typename T> | 
|  | SessionProtoDB<T>::SessionProtoDB( | 
|  | leveldb_proto::ProtoDatabaseProvider* proto_database_provider, | 
|  | const base::FilePath& database_dir, | 
|  | leveldb_proto::ProtoDbType proto_db_type, | 
|  | scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) | 
|  | : SessionProtoStorage<T>(), | 
|  | database_status_(std::nullopt), | 
|  | storage_database_(proto_database_provider->GetDB<T>( | 
|  | proto_db_type, | 
|  | database_dir, | 
|  | base::ThreadPool::CreateSequencedTaskRunner( | 
|  | {base::MayBlock(), base::TaskPriority::USER_VISIBLE}))), | 
|  | ui_thread_task_runner_(ui_thread_task_runner) { | 
|  | static_assert(std::is_base_of<google::protobuf::MessageLite, T>::value, | 
|  | "T must implement 'google::protobuf::MessageLite'"); | 
|  | storage_database_->Init(base::BindOnce(&SessionProtoDB::OnDatabaseInitialized, | 
|  | weak_ptr_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | SessionProtoDB<T>::~SessionProtoDB() = default; | 
|  |  | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::LoadOneEntry(const std::string& key, | 
|  | LoadCallback callback) { | 
|  | if (InitStatusUnknown()) { | 
|  | deferred_operations_.push_back(base::BindOnce( | 
|  | &SessionProtoDB::LoadOneEntry, weak_ptr_factory_.GetWeakPtr(), key, | 
|  | std::move(callback))); | 
|  | } else if (FailedToInit()) { | 
|  | ui_thread_task_runner_->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(std::move(callback), false, std::vector<KeyAndValue>())); | 
|  | } else { | 
|  | storage_database_->GetEntry( | 
|  | key, | 
|  | base::BindOnce(&SessionProtoDB::OnLoadOneEntry, | 
|  | weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::LoadAllEntries(LoadCallback callback) { | 
|  | if (InitStatusUnknown()) { | 
|  | deferred_operations_.push_back( | 
|  | base::BindOnce(&SessionProtoDB::LoadAllEntries, | 
|  | weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } else if (FailedToInit()) { | 
|  | ui_thread_task_runner_->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(std::move(callback), false, std::vector<KeyAndValue>())); | 
|  | } else { | 
|  | storage_database_->LoadEntries( | 
|  | base::BindOnce(&SessionProtoDB::OnLoadContent, | 
|  | weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::LoadContentWithPrefix(const std::string& key_prefix, | 
|  | LoadCallback callback) { | 
|  | if (InitStatusUnknown()) { | 
|  | deferred_operations_.push_back(base::BindOnce( | 
|  | &SessionProtoDB::LoadContentWithPrefix, weak_ptr_factory_.GetWeakPtr(), | 
|  | key_prefix, std::move(callback))); | 
|  | } else if (FailedToInit()) { | 
|  | ui_thread_task_runner_->PostTask( | 
|  | FROM_HERE, | 
|  | base::BindOnce(std::move(callback), false, std::vector<KeyAndValue>())); | 
|  | } else { | 
|  | storage_database_->LoadEntriesWithFilter( | 
|  | base::BindRepeating(&DatabasePrefixFilter, key_prefix), | 
|  | {.fill_cache = false}, | 
|  | /* target_prefix */ "", | 
|  | base::BindOnce(&SessionProtoDB::OnLoadContent, | 
|  | weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::PerformMaintenance( | 
|  | const std::vector<std::string>& keys_to_keep, | 
|  | const std::string& key_substring_to_match, | 
|  | OperationCallback callback) { | 
|  | if (InitStatusUnknown()) { | 
|  | deferred_operations_.push_back(base::BindOnce( | 
|  | &SessionProtoDB::PerformMaintenance, weak_ptr_factory_.GetWeakPtr(), | 
|  | keys_to_keep, key_substring_to_match, std::move(callback))); | 
|  | } else if (FailedToInit()) { | 
|  | ui_thread_task_runner_->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), false)); | 
|  | } else { | 
|  | // The following could be achieved with UpdateEntriesWithRemoveFilter rather | 
|  | // than LoadEntriesWithFilter followed by UpdateEntries, however, that would | 
|  | // not allow metrics to be recorded regarding how much orphaned data was | 
|  | // identified. | 
|  | storage_database_->LoadEntriesWithFilter( | 
|  | base::BindRepeating( | 
|  | [](const std::vector<std::string>& keys_to_keep, | 
|  | const std::string& key_substring_to_match, | 
|  | const std::string& key) { | 
|  | // Return all keys which where key_substring_to_match is a | 
|  | // substring of said keys and hasn't been explicitly marked | 
|  | // not to be removed in keys_to_keep. | 
|  | return base::Contains(key, key_substring_to_match) && | 
|  | !base::Contains(keys_to_keep, key); | 
|  | }, | 
|  | keys_to_keep, key_substring_to_match), | 
|  | base::BindOnce(&SessionProtoDB::OnPerformMaintenance, | 
|  | weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Inserts a value for a given key and passes the result (success/failure) to | 
|  | // OperationCallback. | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::InsertContent(const std::string& key, | 
|  | const T& value, | 
|  | OperationCallback callback) { | 
|  | if (InitStatusUnknown()) { | 
|  | deferred_operations_.push_back(base::BindOnce( | 
|  | &SessionProtoDB::InsertContent, weak_ptr_factory_.GetWeakPtr(), key, | 
|  | std::move(value), std::move(callback))); | 
|  | } else if (FailedToInit()) { | 
|  | ui_thread_task_runner_->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), false)); | 
|  | } else { | 
|  | auto contents_to_save = std::make_unique<ContentEntry>(); | 
|  | contents_to_save->emplace_back(key, value); | 
|  | storage_database_->UpdateEntries( | 
|  | std::move(contents_to_save), | 
|  | std::make_unique<std::vector<std::string>>(), | 
|  | base::BindOnce(&SessionProtoDB::OnOperationCommitted, | 
|  | weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::DeleteOneEntry(const std::string& key, | 
|  | OperationCallback callback) { | 
|  | if (InitStatusUnknown()) { | 
|  | deferred_operations_.push_back(base::BindOnce( | 
|  | &SessionProtoDB::DeleteOneEntry, weak_ptr_factory_.GetWeakPtr(), key, | 
|  | std::move(callback))); | 
|  | } else if (FailedToInit()) { | 
|  | ui_thread_task_runner_->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), false)); | 
|  | } else { | 
|  | auto keys = std::make_unique<std::vector<std::string>>(); | 
|  | keys->push_back(key); | 
|  | storage_database_->UpdateEntries( | 
|  | std::make_unique<ContentEntry>(), std::move(keys), | 
|  | base::BindOnce(&SessionProtoDB::OnOperationCommitted, | 
|  | weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::UpdateEntries( | 
|  | std::unique_ptr<ContentEntry> entries_to_update, | 
|  | std::unique_ptr<std::vector<std::string>> keys_to_remove, | 
|  | OperationCallback callback) { | 
|  | if (InitStatusUnknown()) { | 
|  | deferred_operations_.push_back(base::BindOnce( | 
|  | &SessionProtoDB::UpdateEntries, weak_ptr_factory_.GetWeakPtr(), | 
|  | std::move(entries_to_update), std::move(keys_to_remove), | 
|  | std::move(callback))); | 
|  | } else if (FailedToInit()) { | 
|  | ui_thread_task_runner_->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), false)); | 
|  | } else { | 
|  | storage_database_->UpdateEntries( | 
|  | std::move(entries_to_update), std::move(keys_to_remove), | 
|  | base::BindOnce(&SessionProtoDB::OnOperationCommitted, | 
|  | weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Deletes content in the database, matching all keys which have a prefix | 
|  | // that matches the key. | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::DeleteContentWithPrefix(const std::string& key_prefix, | 
|  | OperationCallback callback) { | 
|  | if (InitStatusUnknown()) { | 
|  | deferred_operations_.push_back(base::BindOnce( | 
|  | &SessionProtoDB::DeleteContentWithPrefix, | 
|  | weak_ptr_factory_.GetWeakPtr(), key_prefix, std::move(callback))); | 
|  | } else if (FailedToInit()) { | 
|  | ui_thread_task_runner_->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), false)); | 
|  |  | 
|  | } else { | 
|  | storage_database_->UpdateEntriesWithRemoveFilter( | 
|  | std::make_unique<ContentEntry>(), | 
|  | base::BindRepeating(&DatabasePrefixFilter, key_prefix), | 
|  | base::BindOnce(&SessionProtoDB::OnOperationCommitted, | 
|  | weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Delete all content in the database. | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::DeleteAllContent(OperationCallback callback) { | 
|  | if (InitStatusUnknown()) { | 
|  | deferred_operations_.push_back( | 
|  | base::BindOnce(&SessionProtoDB::DeleteAllContent, | 
|  | weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } else if (FailedToInit()) { | 
|  | ui_thread_task_runner_->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(callback), false)); | 
|  | } else { | 
|  | storage_database_->Destroy(std::move(callback)); | 
|  | } | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::Destroy() const { | 
|  | // TODO(davidjm): Consider calling the factory's disassociate method here. | 
|  | //                This isn't strictly necessary since it will be called when | 
|  | //                the context is destroyed anyway. | 
|  | } | 
|  |  | 
|  | // Used for tests. | 
|  | template <typename T> | 
|  | SessionProtoDB<T>::SessionProtoDB( | 
|  | std::unique_ptr<leveldb_proto::ProtoDatabase<T>> storage_database, | 
|  | scoped_refptr<base::SequencedTaskRunner> task_runner, | 
|  | scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) | 
|  | : SessionProtoStorage<T>(), | 
|  | database_status_(std::nullopt), | 
|  | storage_database_(std::move(storage_database)), | 
|  | ui_thread_task_runner_(ui_thread_task_runner) { | 
|  | static_assert(std::is_base_of<google::protobuf::MessageLite, T>::value, | 
|  | "T must implement 'google::protobuf::MessageLite'"); | 
|  | storage_database_->Init(base::BindOnce(&SessionProtoDB::OnDatabaseInitialized, | 
|  | weak_ptr_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | // Passes back database status following database initialization. | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::OnDatabaseInitialized( | 
|  | leveldb_proto::Enums::InitStatus status) { | 
|  | database_status_ = | 
|  | std::make_optional<leveldb_proto::Enums::InitStatus>(status); | 
|  | for (auto& deferred_operation : deferred_operations_) { | 
|  | std::move(deferred_operation).Run(); | 
|  | } | 
|  | deferred_operations_.clear(); | 
|  | } | 
|  |  | 
|  | // Callback when one entry is loaded. | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::OnLoadOneEntry(LoadCallback callback, | 
|  | bool success, | 
|  | std::unique_ptr<T> entry) { | 
|  | std::vector<KeyAndValue> results; | 
|  | if (success && entry) { | 
|  | results.emplace_back(entry->key(), *entry); | 
|  | } | 
|  | std::move(callback).Run(success, std::move(results)); | 
|  | } | 
|  |  | 
|  | // Callback when content is loaded. | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::OnLoadContent(LoadCallback callback, | 
|  | bool success, | 
|  | std::unique_ptr<std::vector<T>> content) { | 
|  | std::vector<KeyAndValue> results; | 
|  | if (success) { | 
|  | for (const auto& proto : *content) { | 
|  | // TODO(crbug.com/40161040) relax requirement for proto to have a key | 
|  | // field and return key value pairs OnLoadContent. | 
|  | results.emplace_back(proto.key(), proto); | 
|  | } | 
|  | } | 
|  | std::move(callback).Run(success, std::move(results)); | 
|  | } | 
|  |  | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::OnPerformMaintenance( | 
|  | OperationCallback callback, | 
|  | bool success, | 
|  | std::unique_ptr<std::vector<T>> entries_to_delete) { | 
|  | auto keys_to_delete = std::make_unique<std::vector<std::string>>(); | 
|  | if (success) { | 
|  | for (const auto& proto : *entries_to_delete) { | 
|  | keys_to_delete->emplace_back(proto.key()); | 
|  | } | 
|  | base::UmaHistogramCounts100(kOrphanedDataCountHistogramName, | 
|  | keys_to_delete->size()); | 
|  | } | 
|  | auto save_no_entries = | 
|  | std::make_unique<std::vector<std::pair<std::string, T>>>(); | 
|  | storage_database_->UpdateEntries( | 
|  | std::move(save_no_entries), std::move(keys_to_delete), | 
|  | base::BindOnce(&SessionProtoDB::OnOperationCommitted, | 
|  | weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
|  | } | 
|  |  | 
|  | // Callback when an operation (e.g. insert or delete) is called. | 
|  | template <typename T> | 
|  | void SessionProtoDB<T>::OnOperationCommitted(OperationCallback callback, | 
|  | bool success) { | 
|  | std::move(callback).Run(success); | 
|  | } | 
|  |  | 
|  | // Returns true if initialization status of database is not yet known. | 
|  | template <typename T> | 
|  | bool SessionProtoDB<T>::InitStatusUnknown() const { | 
|  | return database_status_ == std::nullopt; | 
|  | } | 
|  |  | 
|  | // Returns true if the database failed to initialize. | 
|  | template <typename T> | 
|  | bool SessionProtoDB<T>::FailedToInit() const { | 
|  | return database_status_.has_value() && | 
|  | database_status_.value() != leveldb_proto::Enums::InitStatus::kOK; | 
|  | } | 
|  |  | 
|  | #endif  // COMPONENTS_SESSION_PROTO_DB_SESSION_PROTO_DB_H_ |