Home | History | Annotate | Download | only in common
      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_|.
     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|.
     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 
     46 WebDatabase::WebDatabase() {}
     47 
     48 WebDatabase::~WebDatabase() {
     49 }
     50 
     51 void WebDatabase::AddTable(WebDatabaseTable* table) {
     52   tables_[table->GetTypeKey()] = table;
     53 }
     54 
     55 WebDatabaseTable* WebDatabase::GetTable(WebDatabaseTable::TypeKey key) {
     56   return tables_[key];
     57 }
     58 
     59 void WebDatabase::BeginTransaction() {
     60   db_.BeginTransaction();
     61 }
     62 
     63 void WebDatabase::CommitTransaction() {
     64   db_.CommitTransaction();
     65 }
     66 
     67 sql::Connection* WebDatabase::GetSQLConnection() {
     68   return &db_;
     69 }
     70 
     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 
    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 
    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 
    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