1// Copyright (c) 2012 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "components/webdata/common/web_database.h" 6 7#include <algorithm> 8 9#include "base/stl_util.h" 10#include "sql/statement.h" 11#include "sql/transaction.h" 12 13// Current version number. Note: when changing the current version number, 14// corresponding changes must happen in the unit tests, and new migration test 15// added. See |WebDatabaseMigrationTest::kCurrentTestedVersionNumber|. 16// static 17const int WebDatabase::kCurrentVersionNumber = 58; 18 19namespace { 20 21const int kCompatibleVersionNumber = 58; 22 23// Change the version number and possibly the compatibility version of 24// |meta_table_|. 25void ChangeVersion(sql::MetaTable* meta_table, 26 int version_num, 27 bool update_compatible_version_num) { 28 meta_table->SetVersionNumber(version_num); 29 if (update_compatible_version_num) { 30 meta_table->SetCompatibleVersionNumber( 31 std::min(version_num, kCompatibleVersionNumber)); 32 } 33} 34 35// Outputs the failed version number as a warning and always returns 36// |sql::INIT_FAILURE|. 37sql::InitStatus FailedMigrationTo(int version_num) { 38 LOG(WARNING) << "Unable to update web database to version " 39 << version_num << "."; 40 NOTREACHED(); 41 return sql::INIT_FAILURE; 42} 43 44} // namespace 45 46WebDatabase::WebDatabase() {} 47 48WebDatabase::~WebDatabase() { 49} 50 51void WebDatabase::AddTable(WebDatabaseTable* table) { 52 tables_[table->GetTypeKey()] = table; 53} 54 55WebDatabaseTable* WebDatabase::GetTable(WebDatabaseTable::TypeKey key) { 56 return tables_[key]; 57} 58 59void WebDatabase::BeginTransaction() { 60 db_.BeginTransaction(); 61} 62 63void WebDatabase::CommitTransaction() { 64 db_.CommitTransaction(); 65} 66 67sql::Connection* WebDatabase::GetSQLConnection() { 68 return &db_; 69} 70 71sql::InitStatus WebDatabase::Init(const base::FilePath& db_name) { 72 db_.set_histogram_tag("Web"); 73 74 // We don't store that much data in the tables so use a small page size. 75 // This provides a large benefit for empty tables (which is very likely with 76 // the tables we create). 77 db_.set_page_size(2048); 78 79 // We shouldn't have much data and what access we currently have is quite 80 // infrequent. So we go with a small cache size. 81 db_.set_cache_size(32); 82 83 // Run the database in exclusive mode. Nobody else should be accessing the 84 // database while we're running, and this will give somewhat improved perf. 85 db_.set_exclusive_locking(); 86 87 if (!db_.Open(db_name)) 88 return sql::INIT_FAILURE; 89 90 // Initialize various tables 91 sql::Transaction transaction(&db_); 92 if (!transaction.Begin()) 93 return sql::INIT_FAILURE; 94 95 // Version check. 96 if (!meta_table_.Init(&db_, kCurrentVersionNumber, kCompatibleVersionNumber)) 97 return sql::INIT_FAILURE; 98 if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) { 99 LOG(WARNING) << "Web database is too new."; 100 return sql::INIT_TOO_NEW; 101 } 102 103 // Initialize the tables. 104 for (TableMap::iterator it = tables_.begin(); it != tables_.end(); ++it) { 105 it->second->Init(&db_, &meta_table_); 106 } 107 108 // If the file on disk is an older database version, bring it up to date. 109 // If the migration fails we return an error to caller and do not commit 110 // the migration. 111 sql::InitStatus migration_status = MigrateOldVersionsAsNeeded(); 112 if (migration_status != sql::INIT_OK) 113 return migration_status; 114 115 // Create the desired SQL tables if they do not already exist. 116 // It's important that this happen *after* the migration code runs. 117 // Otherwise, the migration code would have to explicitly check for empty 118 // tables created in the new format, and skip the migration in that case. 119 for (TableMap::iterator it = tables_.begin(); it != tables_.end(); ++it) { 120 if (!it->second->CreateTablesIfNecessary()) { 121 LOG(WARNING) << "Unable to initialize the web database."; 122 return sql::INIT_FAILURE; 123 } 124 } 125 126 return transaction.Commit() ? sql::INIT_OK : sql::INIT_FAILURE; 127} 128 129sql::InitStatus WebDatabase::MigrateOldVersionsAsNeeded() { 130 // Some malware used to lower the version number, causing migration to 131 // fail. Ensure the version number is at least as high as the compatible 132 // version number. 133 int current_version = std::max(meta_table_.GetVersionNumber(), 134 meta_table_.GetCompatibleVersionNumber()); 135 if (current_version > meta_table_.GetVersionNumber()) 136 ChangeVersion(&meta_table_, current_version, false); 137 138 if (current_version < 20) { 139 // Versions 1 - 19 are unhandled. Version numbers greater than 140 // kCurrentVersionNumber should have already been weeded out by the caller. 141 // 142 // When the version is too old, we return failure error code. The schema 143 // is too out of date to migrate. 144 // 145 // There should not be a released product that makes a database too old to 146 // migrate. If we do encounter such a legacy database, we will need a 147 // better solution to handle it (i.e., pop up a dialog to tell the user, 148 // erase all their prefs and start over, etc.). 149 LOG(WARNING) << "Web database version " << current_version 150 << " is too old to handle."; 151 NOTREACHED(); 152 return sql::INIT_FAILURE; 153 } 154 155 for (int next_version = current_version + 1; 156 next_version <= kCurrentVersionNumber; 157 ++next_version) { 158 159 // Do any database-wide migrations. 160 bool update_compatible_version = false; 161 if (!MigrateToVersion(next_version, &update_compatible_version)) 162 return FailedMigrationTo(next_version); 163 164 ChangeVersion(&meta_table_, next_version, update_compatible_version); 165 166 // Give each table a chance to migrate to this version. 167 for (TableMap::iterator it = tables_.begin(); it != tables_.end(); ++it) { 168 // Any of the tables may set this to true, but by default it is false. 169 update_compatible_version = false; 170 if (!it->second->MigrateToVersion(next_version, 171 &update_compatible_version)) { 172 return FailedMigrationTo(next_version); 173 } 174 175 ChangeVersion(&meta_table_, next_version, update_compatible_version); 176 } 177 } 178 return sql::INIT_OK; 179} 180 181bool WebDatabase::MigrateToVersion(int version, 182 bool* update_compatible_version) { 183 // Migrate if necessary. 184 switch (version) { 185 case 58: 186 *update_compatible_version = true; 187 return MigrateToVersion58DropWebAppsAndIntents(); 188 } 189 190 return true; 191} 192 193bool WebDatabase::MigrateToVersion58DropWebAppsAndIntents() { 194 sql::Transaction transaction(&db_); 195 return transaction.Begin() && 196 db_.Execute("DROP TABLE IF EXISTS web_apps") && 197 db_.Execute("DROP TABLE IF EXISTS web_app_icons") && 198 db_.Execute("DROP TABLE IF EXISTS web_intents") && 199 db_.Execute("DROP TABLE IF EXISTS web_intents_defaults") && 200 transaction.Commit(); 201} 202