// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/file_manager/indexing/sql_storage.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_functions.h"
#include "sql/error_delegate_util.h"
#include "sql/statement.h"
namespace ash::file_manager {
enum class DbOperationStatus {
kUnknown = 0,
kOpenOk,
kDirectoryCreateError,
kOpenDbError,
kTableInitError,
kDatabaseRazed,
kMaxValue = kDatabaseRazed,
};
SqlStorage::SqlStorage(base::FilePath db_path, const std::string& uma_tag)
: uma_tag_(uma_tag),
db_path_(db_path),
db_(sql::Database(sql::DatabaseOptions())),
token_table_(&db_),
term_table_(&db_),
url_table_(&db_),
file_info_table_(&db_),
posting_list_table_(&db_) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
SqlStorage::~SqlStorage() {
db_.Close();
db_.reset_error_callback();
}
bool SqlStorage::Init() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Make sure we have the directory and open the database on it. Set histogram
// tags, and error handlers.
const base::FilePath db_dir = db_path_.DirName();
if (!base::PathExists(db_dir) && !base::CreateDirectory(db_dir)) {
LOG(ERROR) << "Failed to create a db directory " << db_dir;
base::UmaHistogramEnumeration(uma_tag_,
DbOperationStatus::kDirectoryCreateError);
return false;
}
db_.set_histogram_tag(uma_tag_);
if (!db_.Open(db_path_)) {
LOG(ERROR) << "Failed to open " << db_path_;
base::UmaHistogramEnumeration(uma_tag_, DbOperationStatus::kOpenDbError);
return false;
}
// base::Unretained is safe as SqlStorage (this) owns the sql::Database (db_).
// It thus always outlives it, as destroying this, requires db_ to be
// destroyed first and thus guarantees that the error callback cannot be
// invoked after db_ and this are destroyed.
db_.set_error_callback(base::BindRepeating(&SqlStorage::OnErrorCallback,
base::Unretained(this)));
// Initialize all tables owned by SqlStorage.
if (!InitTables()) {
return false;
}
// Record successful operation and let the world know.
base::UmaHistogramEnumeration(uma_tag_, DbOperationStatus::kOpenOk);
return true;
}
bool SqlStorage::InitTables() {
if (!token_table_.Init()) {
LOG(ERROR) << "Failed to initialize token_table";
base::UmaHistogramEnumeration(uma_tag_, DbOperationStatus::kTableInitError);
return false;
}
if (!term_table_.Init()) {
LOG(ERROR) << "Failed to initialize term_table";
base::UmaHistogramEnumeration(uma_tag_, DbOperationStatus::kTableInitError);
return false;
}
if (!url_table_.Init()) {
LOG(ERROR) << "Failed to initialize url_table";
base::UmaHistogramEnumeration(uma_tag_, DbOperationStatus::kTableInitError);
return false;
}
if (!file_info_table_.Init()) {
LOG(ERROR) << "Failed to initialize file_info_table";
base::UmaHistogramEnumeration(uma_tag_, DbOperationStatus::kTableInitError);
return false;
}
if (!posting_list_table_.Init()) {
LOG(ERROR) << "Failed to initialize file_info_table";
base::UmaHistogramEnumeration(uma_tag_, DbOperationStatus::kTableInitError);
return false;
}
return true;
}
bool SqlStorage::Close() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
db_.Close();
return true;
}
void SqlStorage::OnErrorCallback(int error, sql::Statement* stmt) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LOG(ERROR) << "Database error: " << sql::ToSqliteResultCode(error);
if (stmt) {
LOG(ERROR) << "Database error statement: " << stmt->GetSQLStatement();
}
if (sql::IsErrorCatastrophic(error)) {
LOG(ERROR) << "Database error is catastrophic.";
Restart();
}
}
void SqlStorage::Restart() {
LOG(ERROR) << "Attempting to raze the database.";
if (!db_.Raze()) {
LOG(ERROR) << "Failed to raze the database.";
return;
}
base::UmaHistogramEnumeration(uma_tag_, DbOperationStatus::kDatabaseRazed);
if (InitTables()) {
LOG(ERROR) << "Failed to re-initialize db tables after Raze";
}
}
size_t SqlStorage::AddTermIdsForUrl(const std::set<int64_t>& term_ids,
int64_t url_id) {
size_t added_terms_count = 0;
for (const int64_t term_id : term_ids) {
added_terms_count += AddToPostingList(term_id, url_id);
}
return added_terms_count;
}
size_t SqlStorage::DeleteTermIdsForUrl(const std::set<int64_t>& term_ids,
int64_t url_id) {
size_t deleted_terms_count = 0;
for (const int64_t term_id : term_ids) {
deleted_terms_count += DeleteFromPostingList(term_id, url_id);
}
return deleted_terms_count;
}
const std::set<int64_t> SqlStorage::GetUrlIdsForTermId(int64_t term_id) const {
return posting_list_table_.GetUrlIdsForTerm(term_id);
}
const std::set<int64_t> SqlStorage::GetTermIdsForUrl(int64_t url_id) const {
return posting_list_table_.GetTermIdsForUrl(url_id);
}
size_t SqlStorage::AddToPostingList(int64_t term_id, int64_t url_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(term_id != -1);
DCHECK(url_id != -1);
return posting_list_table_.AddToPostingList(term_id, url_id);
}
size_t SqlStorage::DeleteFromPostingList(int64_t term_id, int64_t url_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(term_id != -1);
DCHECK(url_id != -1);
return posting_list_table_.DeleteFromPostingList(term_id, url_id);
}
int64_t SqlStorage::GetTokenId(const std::string& term_bytes) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return token_table_.GetTokenId(term_bytes);
}
int64_t SqlStorage::GetOrCreateTokenId(const std::string& term_bytes) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return token_table_.GetOrCreateTokenId(term_bytes);
}
int64_t SqlStorage::GetTermId(const Term& term) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
int64_t term_id = GetTokenId(term.token_bytes());
if (term_id == -1) {
return -1;
}
return term_table_.GetTermId(term.field(), term_id);
}
int64_t SqlStorage::GetOrCreateTermId(const Term& term) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
int64_t term_id = GetTermId(term);
if (term_id != -1) {
return term_id;
}
int64_t token_id = GetOrCreateTokenId(term.token_bytes());
return term_table_.GetOrCreateTermId(term.field(), token_id);
}
int64_t SqlStorage::GetOrCreateUrlId(const GURL& url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!url.is_valid()) {
return -1;
}
return url_table_.GetOrCreateUrlId(url);
}
int64_t SqlStorage::GetUrlId(const GURL& url) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!url.is_valid()) {
return -1;
}
return url_table_.GetUrlId(url);
}
int64_t SqlStorage::MoveUrl(const GURL& from, const GURL& to) {
int64_t url_id = GetUrlId(from);
if (url_id == -1) {
return -1;
}
if (from == to) {
return url_id;
}
if (GetUrlId(to) != -1) {
return -1;
}
int64_t moved_url_id = url_table_.ChangeUrl(from, to);
DCHECK(moved_url_id == url_id);
return moved_url_id;
}
int64_t SqlStorage::DeleteUrl(const GURL& url) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return url_table_.DeleteUrl(url);
}
int64_t SqlStorage::PutFileInfo(const FileInfo& file_info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
int64_t url_id = url_table_.GetOrCreateUrlId(file_info.file_url);
if (url_id == -1) {
return -1;
}
return file_info_table_.PutFileInfo(url_id, file_info);
}
std::optional<FileInfo> SqlStorage::GetFileInfo(int64_t url_id) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (url_id == -1) {
return std::nullopt;
}
auto url_spec = url_table_.GetUrlSpec(url_id);
if (!url_spec.has_value()) {
return std::nullopt;
}
GURL url(url_spec.value());
if (!url.is_valid()) {
return std::nullopt;
}
std::optional<FileInfo> file_info = file_info_table_.GetFileInfo(url_id);
if (!file_info.has_value()) {
return std::nullopt;
}
file_info.value().file_url = url;
return file_info;
}
int64_t SqlStorage::DeleteFileInfo(int64_t url_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (url_id == -1) {
return -1;
}
return file_info_table_.DeleteFileInfo(url_id);
}
} // namespace ash::file_manager