Commit b5e9d222 authored by David Van Cleve's avatar David Van Cleve Committed by Commit Bot

Add a simpler API for sqlite_proto initialization.

This CL eases interacting with string-to-proto tables via sqlite_proto
by adding a new TableManager override, ProtoTableManager, which
handles table initialization for the string-to-proto case. Now, clients
just need to construct and initialize one pair of KeyValueTable<T>/
KeyValueData<T> per table.

Bug: 1036494
Change-Id: I4e512cd328f21faaecb57d0ef6c70f00355f6213
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1997659
Commit-Queue: David Van Cleve <davidvc@chromium.org>
Reviewed-by: default avatarAlex Ilin <alexilin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#736471}
parent acb34c2c
...@@ -14,6 +14,8 @@ static_library("sqlite_proto") { ...@@ -14,6 +14,8 @@ static_library("sqlite_proto") {
"key_value_data.h", "key_value_data.h",
"key_value_table.cc", "key_value_table.cc",
"key_value_table.h", "key_value_table.h",
"proto_table_manager.cc",
"proto_table_manager.h",
"table_manager.cc", "table_manager.cc",
"table_manager.h", "table_manager.h",
] ]
...@@ -29,6 +31,7 @@ source_set("unit_tests") { ...@@ -29,6 +31,7 @@ source_set("unit_tests") {
sources = [ sources = [
"key_value_data_unittest.cc", "key_value_data_unittest.cc",
"key_value_table_unittest.cc", "key_value_table_unittest.cc",
"proto_table_manager_unittest.cc",
] ]
deps = [ deps = [
":sqlite_proto", ":sqlite_proto",
......
// Copyright 2020 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.
#include "components/sqlite_proto/proto_table_manager.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/stringprintf.h"
#include "sql/database.h"
namespace sqlite_proto {
namespace {
const char kCreateProtoTableStatementTemplate[] =
"CREATE TABLE %s ( "
"key TEXT, "
"proto BLOB, "
"PRIMARY KEY(key))";
} // namespace
ProtoTableManager::ProtoTableManager(
scoped_refptr<base::SequencedTaskRunner> db_task_runner)
: TableManager(db_task_runner) {}
ProtoTableManager::~ProtoTableManager() = default;
void ProtoTableManager::InitializeOnDbSequence(
sql::Database* db,
base::span<const std::string> table_names) {
DCHECK(std::set<std::string>(table_names.begin(), table_names.end()).size() ==
table_names.size());
DCHECK(!db || db->is_open());
table_names_.assign(table_names.begin(), table_names.end());
Initialize(db); // Superclass method.
}
void ProtoTableManager::CreateTablesIfNonExistent() {
DCHECK(GetTaskRunner()->RunsTasksInCurrentSequence());
if (CantAccessDatabase())
return;
sql::Database* db = DB(); // Superclass method.
bool success = db->BeginTransaction();
for (const std::string& table_name : table_names_) {
success =
success &&
(db->DoesTableExist(table_name.c_str()) ||
db->Execute(base::StringPrintf(kCreateProtoTableStatementTemplate,
table_name.c_str())
.c_str()));
}
if (success)
success = db->CommitTransaction();
else
db->RollbackTransaction();
if (!success)
ResetDB(); // Resets our non-owning pointer; doesn't mutate the database
// object.
}
} // namespace sqlite_proto
// Copyright 2020 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_PROTO_TABLE_MANAGER_H_
#define COMPONENTS_SQLITE_PROTO_PROTO_TABLE_MANAGER_H_
#include <string>
#include "base/callback_forward.h"
#include "base/component_export.h"
#include "base/containers/span.h"
#include "base/sequenced_task_runner.h"
#include "components/sqlite_proto/table_manager.h"
namespace sqlite_proto {
// ProtoTableManager is the instance of TableManager intended to be used
// in the common case of storing string-to-proto tables (alongside
// KeyValueData<T> and KeyValueTable<T>).
//
// End-to-end use:
// 1. Initialize a database with sql::Database::Open.
// 2. Initialize a ProtoTableManager on top of this database.
// 3. For each table, construct a pair of KeyValueTable and KeyValueData
// on the main sequence; initialize the KeyValueData on the database sequence.
// 4. Now, initialization is finished and the KeyValueData objects can be
// used (on the main thread) for get/put/delete operations against the database.
//
// TODO(crbug.com/1046825): This interface is a bit complex and could do with
// a refactor.
class ProtoTableManager : public TableManager {
public:
// Constructor. |db_task_runner| must run tasks on the
// sequence that subsequently calls |InitializeOnDbSequence|.
explicit ProtoTableManager(
scoped_refptr<base::SequencedTaskRunner> db_task_runner);
// Initialization:
// - |table_names| specifies the names of the tables to initialize.
// These must be unique.
// - |db| must be null or point to an open sql::Database.
//
// If |db| is null or initialization fails, subsequent calls to
// ExecuteTaskOnDBThread will silently no-op. (When this class provides the
// backing store for a collection of KeyValueData/KeyValueTable, this leads to
// reasonable fallback behavior of the KeyValueData objects caching data in
// memory.)
void InitializeOnDbSequence(sql::Database* db,
base::span<const std::string> table_names);
protected:
~ProtoTableManager() override;
private:
// TableManager implementation.
void CreateTablesIfNonExistent() override;
// Currently unused.
void LogDatabaseStats() override {}
std::vector<std::string> table_names_;
};
} // namespace sqlite_proto
#endif // COMPONENTS_SQLITE_PROTO_PROTO_TABLE_MANAGER_H_
// Copyright 2020 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.
#include "components/sqlite_proto/proto_table_manager.h"
#include <vector>
#include "base/memory/scoped_refptr.h"
#include "base/task/post_task.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/sqlite_proto/key_value_data.h"
#include "components/sqlite_proto/key_value_table.h"
#include "components/sqlite_proto/test_proto.pb.h"
#include "sql/database.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace sqlite_proto {
namespace {
MATCHER_P(EqualsProto,
message,
"Match a proto Message equal to the matcher's argument.") {
std::string expected_serialized, actual_serialized;
message.SerializeToString(&expected_serialized);
arg.SerializeToString(&actual_serialized);
return expected_serialized == actual_serialized;
}
constexpr char kTableName[] = "my_table";
} // namespace
TEST(ProtoTableTest, PutReinitializeAndGet) {
// In order to test ProtoTableManager is correctly
// initializing the underlying database's tables:
// - create a database and a ProtoTableManager on it;
// - store some data; and
// - construct a new ProtoTableManager to read from the
// existing database state.
base::test::TaskEnvironment env;
sql::Database db;
CHECK(db.OpenInMemory());
auto manager = base::MakeRefCounted<ProtoTableManager>(
base::ThreadTaskRunnerHandle::Get());
manager->InitializeOnDbSequence(&db, std::vector<std::string>{kTableName});
KeyValueTable<TestProto> table(kTableName);
TestProto first_entry, second_entry;
first_entry.set_value(1);
second_entry.set_value(1);
{
KeyValueData<TestProto> data(manager, &table,
/*max_num_entries=*/base::nullopt,
/*flush_delay=*/base::TimeDelta());
// In these tests, we're using the current thread as the DB sequence.
data.InitializeOnDBSequence();
data.UpdateData("a", first_entry);
data.UpdateData("b", second_entry);
env.RunUntilIdle();
}
{
KeyValueData<TestProto> data(manager, &table,
/*max_num_entries=*/base::nullopt,
/*flush_delay=*/base::TimeDelta());
data.InitializeOnDBSequence();
TestProto result;
ASSERT_TRUE(data.TryGetData("a", &result));
EXPECT_THAT(result, EqualsProto(first_entry));
ASSERT_TRUE(data.TryGetData("b", &result));
EXPECT_THAT(result, EqualsProto(second_entry));
}
}
} // namespace sqlite_proto
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment