blob: fcd50e2340989e739dd2b17a4cf57d57490e3a2c [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_SQLITE_PROTO_KEY_VALUE_DATA_H_
#define COMPONENTS_SQLITE_PROTO_KEY_VALUE_DATA_H_
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/location.h"
#include "base/memory/ref_counted.h"
#include "base/optional.h"
#include "base/sequence_checker.h"
#include "base/timer/timer.h"
#include "components/sqlite_proto/key_value_table.h"
#include "components/sqlite_proto/table_manager.h"
namespace sqlite_proto {
namespace internal {
// FakeCompare is a dummy comparator provided so that clients using
// KeyValueData<T> objects with unbounded-size caches need not
// specify the Compare template parameter, which is used exclusively
// for pruning the cache when it would exceed its size bound.
template <typename T>
struct FakeCompare {
bool operator()(const T& unused_lhs, const T& unused_rhs) { return true; }
};
} // namespace internal
// The class provides a synchronous access to the data backed by
// KeyValueTable<T>. The current implementation caches all the
// data in the memory. The cache size is limited by the |max_num_entries|
// parameter, using the Compare function to decide which entries should
// be evicted.
//
// NOTE: If the data store is larger than the maximum cache size, it
// will be pruned on construction to satisfy the size invariant specified
// by |max_num_entries|. If this is undesirable, set a sufficiently high
// |max_num_entries| (or pass |max_num_entries| = base::nullopt for
// unbounded size).
//
// InitializeOnDBSequence() must be called on the DB sequence of the
// TableManager. All other methods must be called on UI thread.
template <typename T, typename Compare = internal::FakeCompare<T>>
class KeyValueData {
public:
// Constructor. Parameters:
// - |manager| provides an interface for scheduling database tasks for
// execution on the database thread.
// - |backend| provides the operations for updating and querying the
// backing database (to be scheduled and executed using |manager|).
// - |max_num_entries|, if given, caps the size of the in-memory cache;
// the Compare template parameter requires a meaningful (in particular,
// non-default) value iff max_num_entries is non-nullopt.
// - |flush_delay| is the interval for which to gather writes and deletes
// passing them through to the backing store; a value of zero will
// pass writes and deletes through immediately.
KeyValueData(scoped_refptr<TableManager> manager,
KeyValueTable<T>* backend,
base::Optional<size_t> max_num_entries,
base::TimeDelta flush_delay);
KeyValueData(const KeyValueData&) = delete;
KeyValueData& operator=(const KeyValueData&) = delete;
// Must be called on the provided TableManager's DB sequence
// before calling all other methods.
void InitializeOnDBSequence();
// Assigns data associated with the |key| to |data|. Returns true iff the
// |key| exists, false otherwise. |data| pointer may be nullptr to get the
// return value only.
bool TryGetData(const std::string& key, T* data) const;
// Returns a view of all the cached data. (The next write or delete may
// invalidate this reference.)
const std::map<std::string, T>& GetAllCached() { return *data_cache_; }
// Assigns data associated with the |key| to |data|.
void UpdateData(const std::string& key, const T& data);
// Deletes data associated with the |keys| from the database.
void DeleteData(const std::vector<std::string>& keys);
// Deletes all entries from the database.
void DeleteAllData();
private:
struct EntryCompare : private Compare {
bool operator()(const std::pair<std::string, T>& lhs,
const std::pair<std::string, T>& rhs) {
return Compare::operator()(lhs.second, rhs.second);
}
};
enum class DeferredOperation { kUpdate, kDelete };
void FlushDataToDisk();
scoped_refptr<TableManager> manager_;
KeyValueTable<T>* backend_table_;
std::unique_ptr<std::map<std::string, T>> data_cache_;
std::unordered_map<std::string, DeferredOperation> deferred_updates_;
base::RepeatingTimer flush_timer_;
const base::TimeDelta flush_delay_;
const base::Optional<size_t> max_num_entries_;
EntryCompare entry_compare_;
SEQUENCE_CHECKER(sequence_checker_);
};
template <typename T, typename Compare>
KeyValueData<T, Compare>::KeyValueData(scoped_refptr<TableManager> manager,
KeyValueTable<T>* backend,
base::Optional<size_t> max_num_entries,
base::TimeDelta flush_delay)
: manager_(manager),
backend_table_(backend),
flush_delay_(flush_delay),
max_num_entries_(max_num_entries) {}
template <typename T, typename Compare>
void KeyValueData<T, Compare>::InitializeOnDBSequence() {
DCHECK(manager_->GetTaskRunner()->RunsTasksInCurrentSequence());
auto data_map = std::make_unique<std::map<std::string, T>>();
manager_->ExecuteDBTaskOnDBSequence(
base::BindOnce(&KeyValueTable<T>::GetAllData, backend_table_->AsWeakPtr(),
data_map.get()));
// To ensure invariant that data_cache_.size() <= max_num_entries_.
std::vector<std::string> keys_to_delete;
while (max_num_entries_.has_value() && data_map->size() > *max_num_entries_) {
auto entry_to_delete =
std::min_element(data_map->begin(), data_map->end(), entry_compare_);
keys_to_delete.emplace_back(entry_to_delete->first);
data_map->erase(entry_to_delete);
}
if (!keys_to_delete.empty()) {
manager_->ExecuteDBTaskOnDBSequence(base::BindOnce(
&KeyValueTable<T>::DeleteData, backend_table_->AsWeakPtr(),
std::vector<std::string>(keys_to_delete)));
}
data_cache_ = std::move(data_map);
}
template <typename T, typename Compare>
bool KeyValueData<T, Compare>::TryGetData(const std::string& key,
T* data) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(data_cache_);
auto it = data_cache_->find(key);
if (it == data_cache_->end())
return false;
if (data)
*data = it->second;
return true;
}
template <typename T, typename Compare>
void KeyValueData<T, Compare>::UpdateData(const std::string& key,
const T& data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(data_cache_);
auto it = data_cache_->find(key);
if (it == data_cache_->end()) {
if (data_cache_->size() == max_num_entries_) {
auto entry_to_delete = std::min_element(
data_cache_->begin(), data_cache_->end(), entry_compare_);
deferred_updates_[entry_to_delete->first] = DeferredOperation::kDelete;
data_cache_->erase(entry_to_delete);
}
data_cache_->emplace(key, data);
} else {
it->second = data;
}
deferred_updates_[key] = DeferredOperation::kUpdate;
if (flush_delay_.is_zero()) {
// Flush immediately, only for tests.
FlushDataToDisk();
} else if (!flush_timer_.IsRunning()) {
flush_timer_.Start(FROM_HERE, flush_delay_, this,
&KeyValueData::FlushDataToDisk);
}
}
template <typename T, typename Compare>
void KeyValueData<T, Compare>::DeleteData(
const std::vector<std::string>& keys) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(data_cache_);
for (const std::string& key : keys) {
if (data_cache_->erase(key))
deferred_updates_[key] = DeferredOperation::kDelete;
}
// Run all deferred updates immediately because it was requested by user.
if (!deferred_updates_.empty())
FlushDataToDisk();
}
template <typename T, typename Compare>
void KeyValueData<T, Compare>::DeleteAllData() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(data_cache_);
data_cache_->clear();
deferred_updates_.clear();
// Delete all the content of the database immediately because it was requested
// by user.
manager_->ScheduleDBTask(FROM_HERE,
base::BindOnce(&KeyValueTable<T>::DeleteAllData,
backend_table_->AsWeakPtr()));
}
template <typename T, typename Compare>
void KeyValueData<T, Compare>::FlushDataToDisk() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (deferred_updates_.empty())
return;
std::vector<std::string> keys_to_delete;
for (const auto& entry : deferred_updates_) {
const std::string& key = entry.first;
switch (entry.second) {
case DeferredOperation::kUpdate: {
auto it = data_cache_->find(key);
if (it != data_cache_->end()) {
manager_->ScheduleDBTask(
FROM_HERE,
base::BindOnce(&KeyValueTable<T>::UpdateData,
backend_table_->AsWeakPtr(), key, it->second));
}
break;
}
case DeferredOperation::kDelete:
keys_to_delete.push_back(key);
}
}
if (!keys_to_delete.empty()) {
manager_->ScheduleDBTask(
FROM_HERE, base::BindOnce(&KeyValueTable<T>::DeleteData,
backend_table_->AsWeakPtr(), keys_to_delete));
}
deferred_updates_.clear();
}
} // namespace sqlite_proto
#endif // COMPONENTS_SQLITE_PROTO_KEY_VALUE_DATA_H_