text: // Copyright (c) 2012 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 "components/webdata/common/web_database.h"
  
  #include <algorithm>
  
  #include "base/stl_util.h"
  #include "content/public/browser/notification_service.h"
  #include "sql/statement.h"
  #include "sql/transaction.h"
  
  // 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|.
  // static
  const int WebDatabase::kCurrentVersionNumber = 50;
  
  namespace {
  
  const int kCompatibleVersionNumber = 48;
  
  // 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::AddTable(WebDatabaseTable* table) {
    tables_[table->GetTypeKey()] = table;
  }
  
  WebDatabaseTable* WebDatabase::GetTable(WebDatabaseTable::TypeKey key) {
    return tables_[key];
  }
  
  void WebDatabase::BeginTransaction() {
    db_.BeginTransaction();
  }
  
  void WebDatabase::CommitTransaction() {
    db_.CommitTransaction();
  }
  
  sql::Connection* WebDatabase::GetSQLConnection() {
    return &db_;
  }
  
  sql::InitStatus WebDatabase::Init(const base::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 (!content::NotificationService::current())
      notification_service_.reset(content::NotificationService::Create());
  
    db_.set_error_histogram_name("Sqlite.Web.Error");
  
    // 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;
    }
  
    // Initialize the tables.
    for (TableMap::iterator it = tables_.begin();
         it != tables_.end();
         ++it) {
      if (!it->second->Init(&db_, &meta_table_)) {
        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() {
    // Some malware used to lower the version number, causing migration to
    // fail. Ensure the version number is at least as high as the compatible
    // version number.
    int current_version = std::max(meta_table_.GetVersionNumber(),
                                   meta_table_.GetCompatibleVersionNumber());
    if (current_version > meta_table_.GetVersionNumber())
      ChangeVersion(&meta_table_, current_version, false);
  
    if (current_version < 20) {
      // Versions 1 - 19 are unhandled.  Version numbers greater than
      // kCurrentVersionNumber should have already been weeded out by the caller.
      //
      // 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;
    }
  
    for (int next_version = current_version + 1;
         next_version <= kCurrentVersionNumber;
         ++next_version) {
      // Give each table a chance to migrate to this version.
      for (TableMap::iterator it = tables_.begin();
           it != tables_.end();
           ++it) {
        // Any of the tables may set this to true, but by default it is false.
        bool update_compatible_version = false;
        if (!it->second->MigrateToVersion(next_version,
                                          &update_compatible_version)) {
          return FailedMigrationTo(next_version);
        }
  
        ChangeVersion(&meta_table_, next_version, update_compatible_version);
      }
    }
    return sql::INIT_OK;
  }
  