// Copyright (c) 2011 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 "chrome/browser/webdata/web_database.h" #include <algorithm> #include "app/sql/statement.h" #include "app/sql/transaction.h" #include "chrome/browser/diagnostics/sqlite_diagnostics.h" #include "content/common/notification_service.h" namespace { // 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|. const int kCurrentVersionNumber = 37; const int kCompatibleVersionNumber = 37; // 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::BeginTransaction() { db_.BeginTransaction(); } void WebDatabase::CommitTransaction() { db_.CommitTransaction(); } AutofillTable* WebDatabase::GetAutofillTable() { return autofill_table_.get(); } KeywordTable* WebDatabase::GetKeywordTable() { return keyword_table_.get(); } LoginsTable* WebDatabase::GetLoginsTable() { return logins_table_.get(); } TokenServiceTable* WebDatabase::GetTokenServiceTable() { return token_service_table_.get(); } WebAppsTable* WebDatabase::GetWebAppsTable() { return web_apps_table_.get(); } sql::Connection* WebDatabase::GetSQLConnection() { return &db_; } sql::InitStatus WebDatabase::Init(const FilePath& db_name) { // When running in unit tests, there is already a NotificationService object. // Since only one can exist at a time per thread, check first. if (!NotificationService::current()) notification_service_.reset(new NotificationService); // Set the exceptional sqlite error handler. db_.set_error_delegate(GetErrorHandlerForWebDb()); // 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_.Open(db_name)) return sql::INIT_FAILURE; // Initialize various tables 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; } // Create the tables. autofill_table_.reset(new AutofillTable(&db_, &meta_table_)); keyword_table_.reset(new KeywordTable(&db_, &meta_table_)); logins_table_.reset(new LoginsTable(&db_, &meta_table_)); token_service_table_.reset(new TokenServiceTable(&db_, &meta_table_)); web_apps_table_.reset(new WebAppsTable(&db_, &meta_table_)); // Initialize the tables. if (!keyword_table_->Init() || !autofill_table_->Init() || !logins_table_->Init() || !web_apps_table_->Init() || !token_service_table_->Init()) { LOG(WARNING) << "Unable to initialize the web database."; return sql::INIT_FAILURE; } // 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; return transaction.Commit() ? sql::INIT_OK : sql::INIT_FAILURE; } sql::InitStatus WebDatabase::MigrateOldVersionsAsNeeded() { // Migrate if necessary. int current_version = meta_table_.GetVersionNumber(); switch (current_version) { // Versions 1 - 19 are unhandled. Version numbers greater than // kCurrentVersionNumber should have already been weeded out by the caller. default: // When the version is too old, we return failure error code. The schema // is too out of date to migrate. // There should not be a released product that makes a database too old to // migrate. If we do encounter such a legacy database, we will need a // better solution to handle it (i.e., pop up a dialog to tell the user, // erase all their prefs and start over, etc.). LOG(WARNING) << "Web database version " << current_version << " is too old to handle."; NOTREACHED(); return sql::INIT_FAILURE; case 20: if (!keyword_table_->MigrateToVersion21AutoGenerateKeywordColumn()) return FailedMigrationTo(21); ChangeVersion(&meta_table_, 21, true); // FALL THROUGH case 21: if (!autofill_table_->ClearAutofillEmptyValueElements()) return FailedMigrationTo(22); ChangeVersion(&meta_table_, 22, false); // FALL THROUGH case 22: if (!autofill_table_->MigrateToVersion23AddCardNumberEncryptedColumn()) return FailedMigrationTo(23); ChangeVersion(&meta_table_, 23, false); // FALL THROUGH case 23: if (!autofill_table_->MigrateToVersion24CleanupOversizedStringFields()) return FailedMigrationTo(24); ChangeVersion(&meta_table_, 24, false); // FALL THROUGH case 24: if (!keyword_table_->MigrateToVersion25AddLogoIDColumn()) return FailedMigrationTo(25); ChangeVersion(&meta_table_, 25, true); // FALL THROUGH case 25: if (!keyword_table_->MigrateToVersion26AddCreatedByPolicyColumn()) return FailedMigrationTo(26); ChangeVersion(&meta_table_, 26, true); // FALL THROUGH case 26: if (!autofill_table_->MigrateToVersion27UpdateLegacyCreditCards()) return FailedMigrationTo(27); ChangeVersion(&meta_table_, 27, true); // FALL THROUGH case 27: if (!keyword_table_->MigrateToVersion28SupportsInstantColumn()) return FailedMigrationTo(28); ChangeVersion(&meta_table_, 28, true); // FALL THROUGH case 28: if (!keyword_table_->MigrateToVersion29InstantUrlToSupportsInstant()) return FailedMigrationTo(29); ChangeVersion(&meta_table_, 29, true); // FALL THROUGH case 29: if (!autofill_table_->MigrateToVersion30AddDateModifed()) return FailedMigrationTo(30); ChangeVersion(&meta_table_, 30, true); // FALL THROUGH case 30: if (!autofill_table_->MigrateToVersion31AddGUIDToCreditCardsAndProfiles()) return FailedMigrationTo(31); ChangeVersion(&meta_table_, 31, true); // FALL THROUGH case 31: if (!autofill_table_->MigrateToVersion32UpdateProfilesAndCreditCards()) return FailedMigrationTo(32); ChangeVersion(&meta_table_, 32, true); // FALL THROUGH case 32: if (!autofill_table_->MigrateToVersion33ProfilesBasedOnFirstName()) return FailedMigrationTo(33); ChangeVersion(&meta_table_, 33, true); // FALL THROUGH case 33: if (!autofill_table_->MigrateToVersion34ProfilesBasedOnCountryCode()) return FailedMigrationTo(34); ChangeVersion(&meta_table_, 34, true); // FALL THROUGH case 34: if (!autofill_table_->MigrateToVersion35GreatBritainCountryCodes()) return FailedMigrationTo(35); ChangeVersion(&meta_table_, 35, true); // FALL THROUGH // Combine migrations 35 and 36. This is due to enhancements to the merge // step when migrating profiles. The original migration from 35 to 36 did // not merge profiles with identical addresses, but the migration from 36 to // 37 does. The step from 35 to 36 should only happen on the Chrome 12 dev // channel. Chrome 12 beta and release users will jump from 35 to 37 // directly getting the full benefits of the multi-valued merge as well as // the culling of bad data. case 35: case 36: if (!autofill_table_->MigrateToVersion37MergeAndCullOlderProfiles()) return FailedMigrationTo(37); ChangeVersion(&meta_table_, 37, true); // FALL THROUGH // Add successive versions here. Each should set the version number and // compatible version number as appropriate, then fall through to the next // case. case kCurrentVersionNumber: // No migration needed. return sql::INIT_OK; } }