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
17 const int WebDatabase::kCurrentVersionNumber = 58;
18
19 namespace {
20
21 const int kCompatibleVersionNumber = 58;
22
23 // Change the version number and possibly the compatibility version of
24 // |meta_table_|.
ChangeVersion(sql::MetaTable * meta_table,int version_num,bool update_compatible_version_num)25 void 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|.
FailedMigrationTo(int version_num)37 sql::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
WebDatabase()46 WebDatabase::WebDatabase() {}
47
~WebDatabase()48 WebDatabase::~WebDatabase() {
49 }
50
AddTable(WebDatabaseTable * table)51 void WebDatabase::AddTable(WebDatabaseTable* table) {
52 tables_[table->GetTypeKey()] = table;
53 }
54
GetTable(WebDatabaseTable::TypeKey key)55 WebDatabaseTable* WebDatabase::GetTable(WebDatabaseTable::TypeKey key) {
56 return tables_[key];
57 }
58
BeginTransaction()59 void WebDatabase::BeginTransaction() {
60 db_.BeginTransaction();
61 }
62
CommitTransaction()63 void WebDatabase::CommitTransaction() {
64 db_.CommitTransaction();
65 }
66
GetSQLConnection()67 sql::Connection* WebDatabase::GetSQLConnection() {
68 return &db_;
69 }
70
Init(const base::FilePath & db_name)71 sql::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
MigrateOldVersionsAsNeeded()129 sql::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
MigrateToVersion(int version,bool * update_compatible_version)181 bool 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
MigrateToVersion58DropWebAppsAndIntents()193 bool 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