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