blob: 1b78846c635f5ab3d69d95b7f0cc9bdb1fbd439c [file] [log] [blame]
// Copyright (c) 2012 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/webdata/common/web_database.h"
#include <algorithm>
#include "base/stl_util.h"
#include "sql/transaction.h"
// Current version number. Note: when changing the current version number,
// corresponding changes must happen in the unit tests, and new migration test
// added. See |WebDatabaseMigrationTest::kCurrentTestedVersionNumber|.
// static
const int WebDatabase::kCurrentVersionNumber = 81;
const int WebDatabase::kDeprecatedVersionNumber = 51;
const base::FilePath::CharType WebDatabase::kInMemoryPath[] =
FILE_PATH_LITERAL(":memory");
namespace {
const int kCompatibleVersionNumber = 79;
// Change the version number and possibly the compatibility version of
// |meta_table_|.
void ChangeVersion(sql::MetaTable* meta_table,
int version_num,
bool update_compatible_version_num) {
meta_table->SetVersionNumber(version_num);
if (update_compatible_version_num) {
meta_table->SetCompatibleVersionNumber(
std::min(version_num, kCompatibleVersionNumber));
}
}
// Outputs the failed version number as a warning and always returns
// |sql::INIT_FAILURE|.
sql::InitStatus FailedMigrationTo(int version_num) {
LOG(WARNING) << "Unable to update web database to version " << version_num
<< ".";
NOTREACHED();
return sql::INIT_FAILURE;
}
} // namespace
WebDatabase::WebDatabase() {}
WebDatabase::~WebDatabase() {}
void WebDatabase::AddTable(WebDatabaseTable* table) {
tables_[table->GetTypeKey()] = table;
}
WebDatabaseTable* WebDatabase::GetTable(WebDatabaseTable::TypeKey key) {
return tables_[key];
}
void WebDatabase::BeginTransaction() {
db_.BeginTransaction();
}
void WebDatabase::CommitTransaction() {
db_.CommitTransaction();
}
std::string WebDatabase::GetDiagnosticInfo(int extended_error,
sql::Statement* statement) {
return db_.GetDiagnosticInfo(extended_error, statement);
}
sql::Database* WebDatabase::GetSQLConnection() {
return &db_;
}
sql::InitStatus WebDatabase::Init(const base::FilePath& db_name) {
db_.set_histogram_tag("Web");
// We don't store that much data in the tables so use a small page size.
// This provides a large benefit for empty tables (which is very likely with
// the tables we create).
db_.set_page_size(2048);
// We shouldn't have much data and what access we currently have is quite
// infrequent. So we go with a small cache size.
db_.set_cache_size(32);
// Run the database in exclusive mode. Nobody else should be accessing the
// database while we're running, and this will give somewhat improved perf.
db_.set_exclusive_locking();
if ((db_name.value() == kInMemoryPath) ? !db_.OpenInMemory()
: !db_.Open(db_name)) {
return sql::INIT_FAILURE;
}
// Clobber really old databases.
static_assert(kDeprecatedVersionNumber < kCurrentVersionNumber,
"Deprecation version must be less than current");
sql::MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersionNumber);
// Scope initialization in a transaction so we can't be partially
// initialized.
sql::Transaction transaction(&db_);
if (!transaction.Begin())
return sql::INIT_FAILURE;
// Version check.
if (!meta_table_.Init(&db_, kCurrentVersionNumber, kCompatibleVersionNumber))
return sql::INIT_FAILURE;
if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
LOG(WARNING) << "Web database is too new.";
return sql::INIT_TOO_NEW;
}
// Initialize the tables.
for (auto it = tables_.begin(); it != tables_.end(); ++it) {
it->second->Init(&db_, &meta_table_);
}
// If the file on disk is an older database version, bring it up to date.
// If the migration fails we return an error to caller and do not commit
// the migration.
sql::InitStatus migration_status = MigrateOldVersionsAsNeeded();
if (migration_status != sql::INIT_OK)
return migration_status;
// Create the desired SQL tables if they do not already exist.
// It's important that this happen *after* the migration code runs.
// Otherwise, the migration code would have to explicitly check for empty
// tables created in the new format, and skip the migration in that case.
for (auto it = tables_.begin(); it != tables_.end(); ++it) {
if (!it->second->CreateTablesIfNecessary()) {
LOG(WARNING) << "Unable to initialize the web database.";
return sql::INIT_FAILURE;
}
}
return transaction.Commit() ? sql::INIT_OK : sql::INIT_FAILURE;
}
sql::InitStatus WebDatabase::MigrateOldVersionsAsNeeded() {
// Some malware used to lower the version number, causing migration to
// fail. Ensure the version number is at least as high as the compatible
// version number.
int current_version = std::max(meta_table_.GetVersionNumber(),
meta_table_.GetCompatibleVersionNumber());
if (current_version > meta_table_.GetVersionNumber())
ChangeVersion(&meta_table_, current_version, false);
DCHECK_GT(current_version, kDeprecatedVersionNumber);
for (int next_version = current_version + 1;
next_version <= kCurrentVersionNumber; ++next_version) {
// Do any database-wide migrations.
bool update_compatible_version = false;
if (!MigrateToVersion(next_version, &update_compatible_version))
return FailedMigrationTo(next_version);
ChangeVersion(&meta_table_, next_version, update_compatible_version);
// Give each table a chance to migrate to this version.
for (auto it = tables_.begin(); it != tables_.end(); ++it) {
// Any of the tables may set this to true, but by default it is false.
update_compatible_version = false;
if (!it->second->MigrateToVersion(next_version,
&update_compatible_version)) {
return FailedMigrationTo(next_version);
}
ChangeVersion(&meta_table_, next_version, update_compatible_version);
}
}
return sql::INIT_OK;
}
bool WebDatabase::MigrateToVersion(int version,
bool* update_compatible_version) {
// Migrate if necessary.
switch (version) {
case 58:
*update_compatible_version = true;
return MigrateToVersion58DropWebAppsAndIntents();
case 79:
*update_compatible_version = true;
return MigrateToVersion79DropLoginsTable();
}
return true;
}
bool WebDatabase::MigrateToVersion58DropWebAppsAndIntents() {
sql::Transaction transaction(&db_);
return transaction.Begin() && db_.Execute("DROP TABLE IF EXISTS web_apps") &&
db_.Execute("DROP TABLE IF EXISTS web_app_icons") &&
db_.Execute("DROP TABLE IF EXISTS web_intents") &&
db_.Execute("DROP TABLE IF EXISTS web_intents_defaults") &&
transaction.Commit();
}
bool WebDatabase::MigrateToVersion79DropLoginsTable() {
sql::Transaction transaction(&db_);
return transaction.Begin() &&
db_.Execute("DROP TABLE IF EXISTS ie7_logins") &&
db_.Execute("DROP TABLE IF EXISTS logins") && transaction.Commit();
}