| // 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; |
| } |
| } |