blob: 4eec4c1415ce902e7aba197ee1c523bfaa1c2de1 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/tab/tab_state_storage_database.h"
#include <memory>
#include <utility>
#include "base/check.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "sql/database.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "sql/transaction.h"
namespace tabs {
namespace {
using OpenTransaction = TabStateStorageDatabase::OpenTransaction;
const int kCurrentVersionNumber = 1;
const int kCompatibleVersionNumber = 1;
constexpr char kTabsTableName[] = "nodes";
bool CreateTable(sql::Database* db, base::cstring_view table_creation_script) {
DCHECK(db->IsSQLValid(table_creation_script));
return db->Execute(table_creation_script);
}
bool CreateSchema(sql::Database* db, sql::MetaTable* meta_table) {
DCHECK(db->HasActiveTransactions());
static constexpr char kCreateTabSchemaSql[] =
"CREATE TABLE IF NOT EXISTS nodes("
"id INTEGER PRIMARY KEY NOT NULL,"
"type INTEGER NOT NULL,"
"children BLOB,"
"payload BLOB)";
return CreateTable(db, kCreateTabSchemaSql);
}
bool InitSchema(sql::Database* db, sql::MetaTable* meta_table) {
bool has_metatable = meta_table->DoesTableExist(db);
bool has_schema = db->DoesTableExist(kTabsTableName);
if (!has_metatable && has_schema) {
db->Raze();
}
sql::Transaction transaction(db);
if (!transaction.Begin()) {
DLOG(ERROR) << "Transaction could not be started.";
return false;
}
if (!meta_table->Init(db, kCurrentVersionNumber, kCompatibleVersionNumber)) {
return false;
}
if (meta_table->GetCompatibleVersionNumber() > kCurrentVersionNumber) {
return false;
}
if (!has_schema && !CreateSchema(db, meta_table)) {
return false;
}
return meta_table->SetVersionNumber(kCurrentVersionNumber) &&
meta_table->SetCompatibleVersionNumber(kCompatibleVersionNumber) &&
transaction.Commit();
}
} // namespace
OpenTransaction::OpenTransaction(std::unique_ptr<sql::Transaction> transaction)
: transaction_(std::move(transaction)) {}
OpenTransaction::~OpenTransaction() = default;
bool OpenTransaction::HasFailed() {
return mark_failed_;
}
void OpenTransaction::MarkFailed() {
mark_failed_ = true;
}
sql::Transaction* OpenTransaction::GetTransaction() {
return transaction_.get();
}
// static
bool TabStateStorageDatabase::OpenTransaction::IsValid(
OpenTransaction* transaction) {
return transaction && !transaction->HasFailed();
}
TabStateStorageDatabase::TabStateStorageDatabase(
const base::FilePath& profile_path)
: profile_path_(profile_path),
db_(std::make_unique<sql::Database>(
sql::Database::Tag("TabStateStorage"))),
meta_table_(std::make_unique<sql::MetaTable>()) {}
TabStateStorageDatabase::~TabStateStorageDatabase() = default;
bool TabStateStorageDatabase::Initialize() {
CHECK(db_);
CHECK(meta_table_);
base::FilePath db_dir = profile_path_.Append(FILE_PATH_LITERAL("Tabs"));
if (!base::CreateDirectory(db_dir)) {
LOG(ERROR) << "Failed to create directory for tab state storage database: "
<< db_dir;
return false;
}
const base::FilePath db_path = db_dir.Append(FILE_PATH_LITERAL("TabDB"));
if (!db_->Open(db_path)) {
LOG(ERROR) << "Failed to open tab state storage database: "
<< db_->GetErrorMessage();
return false;
}
if (!InitSchema(db_.get(), meta_table_.get())) {
DLOG(ERROR) << "Failed to create schema for tab state storage database: "
<< db_->GetErrorMessage();
db_->Close();
return false;
}
return true;
}
bool TabStateStorageDatabase::SaveNode(OpenTransaction* transaction,
int id,
TabStorageType type,
std::string payload,
std::string children) {
CHECK(db_);
DCHECK(OpenTransaction::IsValid(transaction));
static constexpr char kInsertTabSql[] =
"INSERT OR REPLACE INTO nodes"
"(id, type, payload, children)"
"VALUES (?,?,?,?)";
DCHECK(db_->IsSQLValid(kInsertTabSql));
sql::Statement write_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kInsertTabSql));
write_statement.BindInt(0, id);
write_statement.BindInt(1, static_cast<int>(type));
write_statement.BindBlob(2, std::move(payload));
write_statement.BindBlob(3, std::move(children));
return write_statement.Run();
}
bool TabStateStorageDatabase::SaveNodeChildren(OpenTransaction* transaction,
int id,
std::string children) {
CHECK(db_);
DCHECK(OpenTransaction::IsValid(transaction));
static constexpr char kUpdateChildrenSql[] =
"UPDATE nodes"
"SET children = ?"
"WHERE id = ?";
DCHECK(db_->IsSQLValid(kUpdateChildrenSql));
sql::Statement write_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kUpdateChildrenSql));
write_statement.BindInt(0, id);
write_statement.BindBlob(1, std::move(children));
return write_statement.Run();
}
bool TabStateStorageDatabase::RemoveNode(OpenTransaction* transaction, int id) {
CHECK(db_);
DCHECK(OpenTransaction::IsValid(transaction));
static constexpr char kDeleteChildrenSql[] =
"DELETE FROM nodes"
"WHERE id = ?";
DCHECK(db_->IsSQLValid(kDeleteChildrenSql));
sql::Statement write_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kDeleteChildrenSql));
write_statement.BindInt(0, id);
return write_statement.Run();
}
OpenTransaction* TabStateStorageDatabase::CreateTransaction() {
DCHECK(!open_transaction_) << "An open transaction already exists.";
std::unique_ptr<sql::Transaction> transaction =
std::make_unique<sql::Transaction>(db_.get());
OpenTransaction* open_transaction =
new OpenTransaction(std::move(transaction));
sql::Transaction* transaction_ptr = open_transaction->GetTransaction();
if (!transaction_ptr->Begin()) {
DLOG(ERROR) << "Failed to begin transaction.";
open_transaction->MarkFailed();
}
open_transaction_ = base::WrapUnique(open_transaction);
return open_transaction_.get();
}
bool TabStateStorageDatabase::CloseTransaction(
OpenTransaction* open_transaction) {
DCHECK(open_transaction_) << "There is no open transaction.";
sql::Transaction* transaction = open_transaction->GetTransaction();
bool success = false;
if (open_transaction->HasFailed()) {
transaction->Rollback();
DLOG(ERROR) << "Transaction rolled back.";
} else {
success = transaction->Commit();
if (!success) {
DLOG(ERROR) << "Failed to commit transaction.";
// TODO(crbug.com/454005648): If possible, record the reason for commit
// failure here.
}
}
open_transaction_.reset();
return success;
}
std::vector<NodeState> TabStateStorageDatabase::LoadAllNodes() {
std::vector<NodeState> entries;
static constexpr char kSelectAllTabsSql[] =
"SELECT id, type, payload, children FROM nodes";
sql::Statement select_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kSelectAllTabsSql));
while (select_statement.Step()) {
NodeState entry;
entry.id = select_statement.ColumnInt(0);
entry.type = static_cast<TabStorageType>(select_statement.ColumnInt(1));
entry.payload = select_statement.ColumnBlobAsString(2);
entry.children = select_statement.ColumnBlobAsString(3);
entries.emplace_back(std::move(entry));
}
return entries;
}
void TabStateStorageDatabase::ClearAllNodes() {
static constexpr char kDeleteAllTabsSql[] = "DELETE FROM nodes";
sql::Statement delete_statement(
db_->GetCachedStatement(SQL_FROM_HERE, kDeleteAllTabsSql));
delete_statement.Run();
}
} // namespace tabs