普通文本  |  1436行  |  50.95 KB

// 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 "chrome/browser/history/thumbnail_database.h"

#include <algorithm>
#include <string>

#include "base/bind.h"
#include "base/debug/alias.h"
#include "base/file_util.h"
#include "base/format_macros.h"
#include "base/memory/ref_counted_memory.h"
#include "base/metrics/histogram.h"
#include "base/rand_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "chrome/browser/history/url_database.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/dump_without_crashing.h"
#include "sql/recovery.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "third_party/sqlite/sqlite3.h"

#if defined(OS_MACOSX)
#include "base/mac/mac_util.h"
#endif

// Description of database tables:
//
// icon_mapping
//   id               Unique ID.
//   page_url         Page URL which has one or more associated favicons.
//   icon_id          The ID of favicon that this mapping maps to.
//
// favicons           This table associates a row to each favicon for a
//                    |page_url| in the |icon_mapping| table. This is the
//                    default favicon |page_url|/favicon.ico plus any favicons
//                    associated via <link rel="icon_type" href="url">.
//                    The |id| matches the |icon_id| field in the appropriate
//                    row in the icon_mapping table.
//
//   id               Unique ID.
//   url              The URL at which the favicon file is located.
//   icon_type        The type of the favicon specified in the rel attribute of
//                    the link tag. The FAVICON type is used for the default
//                    favicon.ico favicon.
//
// favicon_bitmaps    This table contains the PNG encoded bitmap data of the
//                    favicons. There is a separate row for every size in a
//                    multi resolution bitmap. The bitmap data is associated
//                    to the favicon via the |icon_id| field which matches
//                    the |id| field in the appropriate row in the |favicons|
//                    table.
//
//  id                Unique ID.
//  icon_id           The ID of the favicon that the bitmap is associated to.
//  last_updated      The time at which this favicon was inserted into the
//                    table. This is used to determine if it needs to be
//                    redownloaded from the web.
//  image_data        PNG encoded data of the favicon.
//  width             Pixel width of |image_data|.
//  height            Pixel height of |image_data|.

namespace {

// For this database, schema migrations are deprecated after two
// years.  This means that the oldest non-deprecated version should be
// two years old or greater (thus the migrations to get there are
// older).  Databases containing deprecated versions will be cleared
// at startup.  Since this database is a cache, losing old data is not
// fatal (in fact, very old data may be expired immediately at startup
// anyhow).

// Version 7: 911a634d/r209424 by qsr@chromium.org on 2013-07-01
// Version 6: 610f923b/r152367 by pkotwicz@chromium.org on 2012-08-20
// Version 5: e2ee8ae9/r105004 by groby@chromium.org on 2011-10-12
// Version 4: 5f104d76/r77288 by sky@chromium.org on 2011-03-08 (deprecated)
// Version 3: 09911bf3/r15 by initial.commit on 2008-07-26 (deprecated)

// Version number of the database.
// NOTE(shess): When changing the version, add a new golden file for
// the new version and a test to verify that Init() works with it.
const int kCurrentVersionNumber = 7;
const int kCompatibleVersionNumber = 7;
const int kDeprecatedVersionNumber = 4;  // and earlier.

void FillIconMapping(const sql::Statement& statement,
                     const GURL& page_url,
                     history::IconMapping* icon_mapping) {
  icon_mapping->mapping_id = statement.ColumnInt64(0);
  icon_mapping->icon_id = statement.ColumnInt64(1);
  icon_mapping->icon_type =
      static_cast<chrome::IconType>(statement.ColumnInt(2));
  icon_mapping->icon_url = GURL(statement.ColumnString(3));
  icon_mapping->page_url = page_url;
}

enum InvalidStructureType {
  // NOTE(shess): Intentionally skip bucket 0 to account for
  // conversion from a boolean histogram.
  STRUCTURE_EVENT_FAVICON = 1,
  STRUCTURE_EVENT_VERSION4,
  STRUCTURE_EVENT_VERSION5,

  // Always keep this at the end.
  STRUCTURE_EVENT_MAX,
};

void RecordInvalidStructure(InvalidStructureType invalid_type) {
  UMA_HISTOGRAM_ENUMERATION("History.InvalidFaviconsDBStructure",
                            invalid_type, STRUCTURE_EVENT_MAX);
}

// Attempt to pass 2000 bytes of |debug_info| into a crash dump.
void DumpWithoutCrashing2000(const std::string& debug_info) {
  char debug_buf[2000];
  base::strlcpy(debug_buf, debug_info.c_str(), arraysize(debug_buf));
  base::debug::Alias(&debug_buf);

  logging::DumpWithoutCrashing();
}

void ReportCorrupt(sql::Connection* db, size_t startup_kb) {
  // Buffer for accumulating debugging info about the error.  Place
  // more-relevant information earlier, in case things overflow the
  // fixed-size buffer.
  std::string debug_info;

  base::StringAppendF(&debug_info, "SQLITE_CORRUPT, integrity_check:\n");

  // Check files up to 8M to keep things from blocking too long.
  const size_t kMaxIntegrityCheckSize = 8192;
  if (startup_kb > kMaxIntegrityCheckSize) {
    base::StringAppendF(&debug_info, "too big %" PRIuS "\n", startup_kb);
  } else {
    std::vector<std::string> messages;

    const base::TimeTicks before = base::TimeTicks::Now();
    db->FullIntegrityCheck(&messages);
    base::StringAppendF(&debug_info, "# %" PRIx64 " ms, %" PRIuS " records\n",
                        (base::TimeTicks::Now() - before).InMilliseconds(),
                        messages.size());

    // SQLite returns up to 100 messages by default, trim deeper to
    // keep close to the 2000-character size limit for dumping.
    //
    // TODO(shess): If the first 20 tend to be actionable, test if
    // passing the count to integrity_check makes it exit earlier.  In
    // that case it may be possible to greatly ease the size
    // restriction.
    const size_t kMaxMessages = 20;
    for (size_t i = 0; i < kMaxMessages && i < messages.size(); ++i) {
      base::StringAppendF(&debug_info, "%s\n", messages[i].c_str());
    }
  }

  DumpWithoutCrashing2000(debug_info);
}

void ReportError(sql::Connection* db, int error) {
  // Buffer for accumulating debugging info about the error.  Place
  // more-relevant information earlier, in case things overflow the
  // fixed-size buffer.
  std::string debug_info;

  // The error message from the failed operation.
  base::StringAppendF(&debug_info, "db error: %d/%s\n",
                      db->GetErrorCode(), db->GetErrorMessage());

  // System errno information.
  base::StringAppendF(&debug_info, "errno: %d\n", db->GetLastErrno());

  // SQLITE_ERROR reports seem to be attempts to upgrade invalid
  // schema, try to log that info.
  if (error == SQLITE_ERROR) {
    const char* kVersionSql = "SELECT value FROM meta WHERE key = 'version'";
    if (db->IsSQLValid(kVersionSql)) {
      sql::Statement statement(db->GetUniqueStatement(kVersionSql));
      if (statement.Step()) {
        debug_info += "version: ";
        debug_info += statement.ColumnString(0);
        debug_info += '\n';
      } else if (statement.Succeeded()) {
        debug_info += "version: none\n";
      } else {
        debug_info += "version: error\n";
      }
    } else {
      debug_info += "version: invalid\n";
    }

    debug_info += "schema:\n";

    // sqlite_master has columns:
    //   type - "index" or "table".
    //   name - name of created element.
    //   tbl_name - name of element, or target table in case of index.
    //   rootpage - root page of the element in database file.
    //   sql - SQL to create the element.
    // In general, the |sql| column is sufficient to derive the other
    // columns.  |rootpage| is not interesting for debugging, without
    // the contents of the database.  The COALESCE is because certain
    // automatic elements will have a |name| but no |sql|,
    const char* kSchemaSql = "SELECT COALESCE(sql, name) FROM sqlite_master";
    sql::Statement statement(db->GetUniqueStatement(kSchemaSql));
    while (statement.Step()) {
      debug_info += statement.ColumnString(0);
      debug_info += '\n';
    }
    if (!statement.Succeeded())
      debug_info += "error\n";
  }

  // TODO(shess): Think of other things to log.  Not logging the
  // statement text because the backtrace should suffice in most
  // cases.  The database schema is a possibility, but the
  // likelihood of recursive error callbacks makes that risky (same
  // reasoning applies to other data fetched from the database).

  DumpWithoutCrashing2000(debug_info);
}

// TODO(shess): If this proves out, perhaps lift the code out to
// chrome/browser/diagnostics/sqlite_diagnostics.{h,cc}.
void GenerateDiagnostics(sql::Connection* db,
                         size_t startup_kb,
                         int extended_error) {
  int error = (extended_error & 0xFF);

  // Infrequently report information about the error up to the crash
  // server.
  static const uint64 kReportsPerMillion = 50000;

  // Since some/most errors will not resolve themselves, only report
  // once per Chrome run.
  static bool reported = false;
  if (reported)
    return;

  uint64 rand = base::RandGenerator(1000000);
  if (error == SQLITE_CORRUPT) {
    // Once the database is known to be corrupt, it will generate a
    // stream of errors until someone fixes it, so give one chance.
    // Set first in case of errors in generating the report.
    reported = true;

    // Corrupt cases currently dominate, report them very infrequently.
    static const uint64 kCorruptReportsPerMillion = 10000;
    if (rand < kCorruptReportsPerMillion)
      ReportCorrupt(db, startup_kb);
  } else if (error == SQLITE_READONLY) {
    // SQLITE_READONLY appears similar to SQLITE_CORRUPT - once it
    // is seen, it is almost guaranteed to be seen again.
    reported = true;

    if (rand < kReportsPerMillion)
      ReportError(db, extended_error);
  } else {
    // Only set the flag when making a report.  This should allow
    // later (potentially different) errors in a stream of errors to
    // be reported.
    //
    // TODO(shess): Would it be worthwile to audit for which cases
    // want once-only handling?  Sqlite.Error.Thumbnail shows
    // CORRUPT and READONLY as almost 95% of all reports on these
    // channels, so probably easier to just harvest from the field.
    if (rand < kReportsPerMillion) {
      reported = true;
      ReportError(db, extended_error);
    }
  }
}

// Create v5 schema for recovery code.
bool InitSchemaV5(sql::Connection* db) {
  // This schema was derived from the strings used when v5 was in
  // force.  The [favicons] index and the [icon_mapping] items were
  // copied from the current strings, after verifying that the
  // resulting schema exactly matches the schema created by the
  // original versions of those strings.  This allows the linker to
  // share the strings if they match, while preferring correctness of
  // the current versions change.

  const char kFaviconsV5[] =
      "CREATE TABLE IF NOT EXISTS favicons("
      "id INTEGER PRIMARY KEY,"
      "url LONGVARCHAR NOT NULL,"
      "last_updated INTEGER DEFAULT 0,"
      "image_data BLOB,"
      "icon_type INTEGER DEFAULT 1,"
      "sizes LONGVARCHAR"
      ")";
  const char kFaviconsIndexV5[] =
      "CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)";
  if (!db->Execute(kFaviconsV5) || !db->Execute(kFaviconsIndexV5))
    return false;

  const char kIconMappingV5[] =
      "CREATE TABLE IF NOT EXISTS icon_mapping"
      "("
      "id INTEGER PRIMARY KEY,"
      "page_url LONGVARCHAR NOT NULL,"
      "icon_id INTEGER"
      ")";
  const char kIconMappingUrlIndexV5[] =
      "CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx"
      " ON icon_mapping(page_url)";
  const char kIconMappingIdIndexV5[] =
      "CREATE INDEX IF NOT EXISTS icon_mapping_icon_id_idx"
      " ON icon_mapping(icon_id)";
  if (!db->Execute(kIconMappingV5) ||
      !db->Execute(kIconMappingUrlIndexV5) ||
      !db->Execute(kIconMappingIdIndexV5)) {
    return false;
  }

  return true;
}

// TODO(shess): Consider InitSchemaV7().  InitSchemaV5() is worthwhile
// because there appear to be 10s of thousands of marooned v5
// databases in the wild.  Once recovery reaches stable, the number of
// corrupt-but-recoverable databases should drop, possibly to the
// point where it is not worthwhile to maintain previous-version
// recovery code.
// TODO(shess): Alternately, think on a way to more cleanly represent
// versioned schema going forward.
bool InitTables(sql::Connection* db) {
  const char kIconMappingSql[] =
      "CREATE TABLE IF NOT EXISTS icon_mapping"
      "("
      "id INTEGER PRIMARY KEY,"
      "page_url LONGVARCHAR NOT NULL,"
      "icon_id INTEGER"
      ")";
  if (!db->Execute(kIconMappingSql))
    return false;

  const char kFaviconsSql[] =
      "CREATE TABLE IF NOT EXISTS favicons"
      "("
      "id INTEGER PRIMARY KEY,"
      "url LONGVARCHAR NOT NULL,"
      // default icon_type FAVICON to be consistent with past migration.
      "icon_type INTEGER DEFAULT 1"
      ")";
  if (!db->Execute(kFaviconsSql))
    return false;

  const char kFaviconBitmapsSql[] =
      "CREATE TABLE IF NOT EXISTS favicon_bitmaps"
      "("
      "id INTEGER PRIMARY KEY,"
      "icon_id INTEGER NOT NULL,"
      "last_updated INTEGER DEFAULT 0,"
      "image_data BLOB,"
      "width INTEGER DEFAULT 0,"
      "height INTEGER DEFAULT 0"
      ")";
  if (!db->Execute(kFaviconBitmapsSql))
    return false;

  return true;
}

bool InitIndices(sql::Connection* db) {
  const char kIconMappingUrlIndexSql[] =
      "CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx"
      " ON icon_mapping(page_url)";
  const char kIconMappingIdIndexSql[] =
      "CREATE INDEX IF NOT EXISTS icon_mapping_icon_id_idx"
      " ON icon_mapping(icon_id)";
  if (!db->Execute(kIconMappingUrlIndexSql) ||
      !db->Execute(kIconMappingIdIndexSql)) {
    return false;
  }

  const char kFaviconsIndexSql[] =
      "CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)";
  if (!db->Execute(kFaviconsIndexSql))
    return false;

  const char kFaviconBitmapsIndexSql[] =
      "CREATE INDEX IF NOT EXISTS favicon_bitmaps_icon_id ON "
      "favicon_bitmaps(icon_id)";
  if (!db->Execute(kFaviconBitmapsIndexSql))
    return false;

  return true;
}

enum RecoveryEventType {
  RECOVERY_EVENT_RECOVERED = 0,
  RECOVERY_EVENT_FAILED_SCOPER,
  RECOVERY_EVENT_FAILED_META_VERSION_ERROR,  // obsolete
  RECOVERY_EVENT_FAILED_META_VERSION_NONE,  // obsolete
  RECOVERY_EVENT_FAILED_META_WRONG_VERSION6,  // obsolete
  RECOVERY_EVENT_FAILED_META_WRONG_VERSION5,  // obsolete
  RECOVERY_EVENT_FAILED_META_WRONG_VERSION,
  RECOVERY_EVENT_FAILED_RECOVER_META,  // obsolete
  RECOVERY_EVENT_FAILED_META_INSERT,  // obsolete
  RECOVERY_EVENT_FAILED_INIT,
  RECOVERY_EVENT_FAILED_RECOVER_FAVICONS,  // obsolete
  RECOVERY_EVENT_FAILED_FAVICONS_INSERT,  // obsolete
  RECOVERY_EVENT_FAILED_RECOVER_FAVICON_BITMAPS,  // obsolete
  RECOVERY_EVENT_FAILED_FAVICON_BITMAPS_INSERT,  // obsolete
  RECOVERY_EVENT_FAILED_RECOVER_ICON_MAPPING,  // obsolete
  RECOVERY_EVENT_FAILED_ICON_MAPPING_INSERT,  // obsolete
  RECOVERY_EVENT_RECOVERED_VERSION6,
  RECOVERY_EVENT_FAILED_META_INIT,
  RECOVERY_EVENT_FAILED_META_VERSION,
  RECOVERY_EVENT_DEPRECATED,
  RECOVERY_EVENT_FAILED_V5_INITSCHEMA,
  RECOVERY_EVENT_FAILED_V5_AUTORECOVER_FAVICONS,
  RECOVERY_EVENT_FAILED_V5_AUTORECOVER_ICON_MAPPING,
  RECOVERY_EVENT_RECOVERED_VERSION5,
  RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS,
  RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS,
  RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING,
  RECOVERY_EVENT_FAILED_COMMIT,

  // Always keep this at the end.
  RECOVERY_EVENT_MAX,
};

void RecordRecoveryEvent(RecoveryEventType recovery_event) {
  UMA_HISTOGRAM_ENUMERATION("History.FaviconsRecovery",
                            recovery_event, RECOVERY_EVENT_MAX);
}

// Recover the database to the extent possible, razing it if recovery
// is not possible.
// TODO(shess): This is mostly just a safe proof of concept.  In the
// real world, this database is probably not worthwhile recovering, as
// opposed to just razing it and starting over whenever corruption is
// detected.  So this database is a good test subject.
void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) {
  // NOTE(shess): This code is currently specific to the version
  // number.  I am working on simplifying things to loosen the
  // dependency, meanwhile contact me if you need to bump the version.
  DCHECK_EQ(7, kCurrentVersionNumber);

  // TODO(shess): Reset back after?
  db->reset_error_callback();

  // For histogram purposes.
  size_t favicons_rows_recovered = 0;
  size_t favicon_bitmaps_rows_recovered = 0;
  size_t icon_mapping_rows_recovered = 0;
  int64 original_size = 0;
  base::GetFileSize(db_path, &original_size);

  scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path);
  if (!recovery) {
    // TODO(shess): Unable to create recovery connection.  This
    // implies something substantial is wrong.  At this point |db| has
    // been poisoned so there is nothing really to do.
    //
    // Possible responses are unclear.  If the failure relates to a
    // problem somehow specific to the temporary file used to back the
    // database, then an in-memory database could possibly be used.
    // This could potentially allow recovering the main database, and
    // might be simple to implement w/in Begin().
    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_SCOPER);
    return;
  }

  // Setup the meta recovery table and fetch the version number from
  // the corrupt database.
  int version = 0;
  if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) {
    // TODO(shess): Prior histograms indicate all failures are in
    // creating the recover virtual table for corrupt.meta.  The table
    // may not exist, or the database may be too far gone.  Either
    // way, unclear how to resolve.
    sql::Recovery::Rollback(recovery.Pass());
    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_VERSION);
    return;
  }

  // Recover v5 database to v5 schema.  Next pass through Init() will
  // migrate to v7.
  if (version == 5) {
    sql::MetaTable recover_meta_table;
    if (!recover_meta_table.Init(recovery->db(), version, version)) {
      sql::Recovery::Rollback(recovery.Pass());
      RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT);
      return;
    }

    // TODO(shess): These tests are separate for histogram purposes,
    // but once things look stable it can be tightened up.
    if (!InitSchemaV5(recovery->db())) {
      sql::Recovery::Rollback(recovery.Pass());
      RecordRecoveryEvent(RECOVERY_EVENT_FAILED_V5_INITSCHEMA);
      return;
    }

    if (!recovery->AutoRecoverTable("favicons", 0, &favicons_rows_recovered)) {
      sql::Recovery::Rollback(recovery.Pass());
      RecordRecoveryEvent(RECOVERY_EVENT_FAILED_V5_AUTORECOVER_FAVICONS);
      return;
    }

    if (!recovery->AutoRecoverTable("icon_mapping", 0,
                                    &icon_mapping_rows_recovered)) {
      sql::Recovery::Rollback(recovery.Pass());
      RecordRecoveryEvent(RECOVERY_EVENT_FAILED_V5_AUTORECOVER_ICON_MAPPING);
      return;
    }

    ignore_result(sql::Recovery::Recovered(recovery.Pass()));

    // TODO(shess): Could this code be shared with the v6/7 code
    // without requiring too much state to be carried?

    // Track the size of the recovered database relative to the size of
    // the input database.  The size should almost always be smaller,
    // unless the input database was empty to start with.  If the
    // percentage results are very low, something is awry.
    int64 final_size = 0;
    if (original_size > 0 &&
        base::GetFileSize(db_path, &final_size) &&
        final_size > 0) {
      UMA_HISTOGRAM_PERCENTAGE("History.FaviconsRecoveredPercentage",
                               final_size * 100 / original_size);
    }

    // Using 10,000 because these cases mostly care about "none
    // recovered" and "lots recovered".  More than 10,000 rows recovered
    // probably means there's something wrong with the profile.
    UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFavicons",
                               favicons_rows_recovered);
    UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsIconMapping",
                               icon_mapping_rows_recovered);

    RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED_VERSION5);
    return;
  }

  // This code may be able to fetch versions that the regular
  // deprecation path cannot.
  if (version <= kDeprecatedVersionNumber) {
    sql::Recovery::Unrecoverable(recovery.Pass());
    RecordRecoveryEvent(RECOVERY_EVENT_DEPRECATED);
    return;
  }

  // TODO(shess): Earlier versions have been handled or deprecated,
  // later versions should be impossible.  Unrecoverable() seems
  // reasonable.
  if (version != 6 && version != 7) {
    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION);
    sql::Recovery::Rollback(recovery.Pass());
    return;
  }

  // Both v6 and v7 recover to current schema version.
  sql::MetaTable recover_meta_table;
  if (!recover_meta_table.Init(recovery->db(), kCurrentVersionNumber,
                               kCompatibleVersionNumber)) {
    sql::Recovery::Rollback(recovery.Pass());
    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT);
    return;
  }

  // Create a fresh version of the database.  The recovery code uses
  // conflict-resolution to handle duplicates, so the indices are
  // necessary.
  if (!InitTables(recovery->db()) || !InitIndices(recovery->db())) {
    // TODO(shess): Unable to create the new schema in the new
    // database.  The new database should be a temporary file, so
    // being unable to work with it is pretty unclear.
    //
    // What are the potential responses, even?  The recovery database
    // could be opened as in-memory.  If the temp database had a
    // filesystem problem and the temp filesystem differs from the
    // main database, then that could fix it.
    sql::Recovery::Rollback(recovery.Pass());
    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_INIT);
    return;
  }

  // [favicons] differs because v6 had an unused [sizes] column which
  // was removed in v7.
  if (!recovery->AutoRecoverTable("favicons", 1, &favicons_rows_recovered)) {
    sql::Recovery::Rollback(recovery.Pass());
    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS);
    return;
  }
  if (!recovery->AutoRecoverTable("favicon_bitmaps", 0,
                                  &favicon_bitmaps_rows_recovered)) {
    sql::Recovery::Rollback(recovery.Pass());
    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS);
    return;
  }
  if (!recovery->AutoRecoverTable("icon_mapping", 0,
                                  &icon_mapping_rows_recovered)) {
    sql::Recovery::Rollback(recovery.Pass());
    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING);
    return;
  }

  // TODO(shess): Is it possible/likely to have broken foreign-key
  // issues with the tables?
  // - icon_mapping.icon_id maps to no favicons.id
  // - favicon_bitmaps.icon_id maps to no favicons.id
  // - favicons.id is referenced by no icon_mapping.icon_id
  // - favicons.id is referenced by no favicon_bitmaps.icon_id
  // This step is possibly not worth the effort necessary to develop
  // and sequence the statements, as it is basically a form of garbage
  // collection.

  if (!sql::Recovery::Recovered(recovery.Pass())) {
    RecordRecoveryEvent(RECOVERY_EVENT_FAILED_COMMIT);
    return;
  }

  // Track the size of the recovered database relative to the size of
  // the input database.  The size should almost always be smaller,
  // unless the input database was empty to start with.  If the
  // percentage results are very low, something is awry.
  int64 final_size = 0;
  if (original_size > 0 &&
      base::GetFileSize(db_path, &final_size) &&
      final_size > 0) {
    int percentage = static_cast<int>(original_size * 100 / final_size);
    UMA_HISTOGRAM_PERCENTAGE("History.FaviconsRecoveredPercentage",
                             std::max(100, percentage));
  }

  // Using 10,000 because these cases mostly care about "none
  // recovered" and "lots recovered".  More than 10,000 rows recovered
  // probably means there's something wrong with the profile.
  UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFavicons",
                             favicons_rows_recovered);
  UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFaviconBitmaps",
                             favicon_bitmaps_rows_recovered);
  UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsIconMapping",
                             icon_mapping_rows_recovered);

  if (version == 6) {
    RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED_VERSION6);
  } else {
    RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED);
  }
}

void DatabaseErrorCallback(sql::Connection* db,
                           const base::FilePath& db_path,
                           size_t startup_kb,
                           int extended_error,
                           sql::Statement* stmt) {
  // TODO(shess): Assert that this is running on a safe thread.
  // AFAICT, should be the history thread, but at this level I can't
  // see how to reach that.

  // TODO(shess): For now, don't report on beta or stable so as not to
  // overwhelm the crash server.  Once the big fish are fried,
  // consider reporting at a reduced rate on the bigger channels.
  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
  if (channel != chrome::VersionInfo::CHANNEL_STABLE &&
      channel != chrome::VersionInfo::CHANNEL_BETA) {
    GenerateDiagnostics(db, startup_kb, extended_error);
  }

  // Attempt to recover corrupt databases.
  int error = (extended_error & 0xFF);
  if (error == SQLITE_CORRUPT ||
      error == SQLITE_CANTOPEN ||
      error == SQLITE_NOTADB) {
    RecoverDatabaseOrRaze(db, db_path);
  }

  // The default handling is to assert on debug and to ignore on release.
  if (!sql::Connection::ShouldIgnoreSqliteError(extended_error))
    DLOG(FATAL) << db->GetErrorMessage();
}

}  // namespace

namespace history {

ThumbnailDatabase::IconMappingEnumerator::IconMappingEnumerator() {
}

ThumbnailDatabase::IconMappingEnumerator::~IconMappingEnumerator() {
}

bool ThumbnailDatabase::IconMappingEnumerator::GetNextIconMapping(
    IconMapping* icon_mapping) {
  if (!statement_.Step())
    return false;
  FillIconMapping(statement_, GURL(statement_.ColumnString(4)), icon_mapping);
  return true;
}

ThumbnailDatabase::ThumbnailDatabase() {
}

ThumbnailDatabase::~ThumbnailDatabase() {
  // The DBCloseScoper will delete the DB and the cache.
}

sql::InitStatus ThumbnailDatabase::Init(const base::FilePath& db_name) {
  // TODO(shess): Consider separating database open from schema setup.
  // With that change, this code could Raze() from outside the
  // transaction, rather than needing RazeAndClose() in InitImpl().

  // Retry failed setup in case the recovery system fixed things.
  const size_t kAttempts = 2;

  sql::InitStatus status = sql::INIT_FAILURE;
  for (size_t i = 0; i < kAttempts; ++i) {
    status = InitImpl(db_name);
    if (status == sql::INIT_OK)
      return status;

    meta_table_.Reset();
    db_.Close();
  }
  return status;
}

void ThumbnailDatabase::ComputeDatabaseMetrics() {
  sql::Statement favicon_count(
      db_.GetCachedStatement(SQL_FROM_HERE, "SELECT COUNT(*) FROM favicons"));
  UMA_HISTOGRAM_COUNTS_10000(
      "History.NumFaviconsInDB",
      favicon_count.Step() ? favicon_count.ColumnInt(0) : 0);
}

void ThumbnailDatabase::BeginTransaction() {
  db_.BeginTransaction();
}

void ThumbnailDatabase::CommitTransaction() {
  db_.CommitTransaction();
}

void ThumbnailDatabase::RollbackTransaction() {
  db_.RollbackTransaction();
}

void ThumbnailDatabase::Vacuum() {
  DCHECK(db_.transaction_nesting() == 0) <<
      "Can not have a transaction when vacuuming.";
  ignore_result(db_.Execute("VACUUM"));
}

void ThumbnailDatabase::TrimMemory(bool aggressively) {
  db_.TrimMemory(aggressively);
}

bool ThumbnailDatabase::GetFaviconBitmapIDSizes(
    chrome::FaviconID icon_id,
    std::vector<FaviconBitmapIDSize>* bitmap_id_sizes) {
  DCHECK(icon_id);
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "SELECT id, width, height FROM favicon_bitmaps WHERE icon_id=?"));
  statement.BindInt64(0, icon_id);

  bool result = false;
  while (statement.Step()) {
    result = true;
    if (!bitmap_id_sizes)
      return result;

    FaviconBitmapIDSize bitmap_id_size;
    bitmap_id_size.bitmap_id = statement.ColumnInt64(0);
    bitmap_id_size.pixel_size = gfx::Size(statement.ColumnInt(1),
                                          statement.ColumnInt(2));
    bitmap_id_sizes->push_back(bitmap_id_size);
  }
  return result;
}

bool ThumbnailDatabase::GetFaviconBitmaps(
    chrome::FaviconID icon_id,
    std::vector<FaviconBitmap>* favicon_bitmaps) {
  DCHECK(icon_id);
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "SELECT id, last_updated, image_data, width, height FROM favicon_bitmaps "
      "WHERE icon_id=?"));
  statement.BindInt64(0, icon_id);

  bool result = false;
  while (statement.Step()) {
    result = true;
    if (!favicon_bitmaps)
      return result;

    FaviconBitmap favicon_bitmap;
    favicon_bitmap.bitmap_id = statement.ColumnInt64(0);
    favicon_bitmap.icon_id = icon_id;
    favicon_bitmap.last_updated =
        base::Time::FromInternalValue(statement.ColumnInt64(1));
    if (statement.ColumnByteLength(2) > 0) {
      scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes());
      statement.ColumnBlobAsVector(2, &data->data());
      favicon_bitmap.bitmap_data = data;
    }
    favicon_bitmap.pixel_size = gfx::Size(statement.ColumnInt(3),
                                          statement.ColumnInt(4));
    favicon_bitmaps->push_back(favicon_bitmap);
  }
  return result;
}

bool ThumbnailDatabase::GetFaviconBitmap(
    FaviconBitmapID bitmap_id,
    base::Time* last_updated,
    scoped_refptr<base::RefCountedMemory>* png_icon_data,
    gfx::Size* pixel_size) {
  DCHECK(bitmap_id);
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "SELECT last_updated, image_data, width, height FROM favicon_bitmaps "
      "WHERE id=?"));
  statement.BindInt64(0, bitmap_id);

  if (!statement.Step())
    return false;

  if (last_updated)
    *last_updated = base::Time::FromInternalValue(statement.ColumnInt64(0));

  if (png_icon_data && statement.ColumnByteLength(1) > 0) {
    scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes());
    statement.ColumnBlobAsVector(1, &data->data());
    *png_icon_data = data;
  }

  if (pixel_size) {
    *pixel_size = gfx::Size(statement.ColumnInt(2),
                            statement.ColumnInt(3));
  }
  return true;
}

FaviconBitmapID ThumbnailDatabase::AddFaviconBitmap(
    chrome::FaviconID icon_id,
    const scoped_refptr<base::RefCountedMemory>& icon_data,
    base::Time time,
    const gfx::Size& pixel_size) {
  DCHECK(icon_id);
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "INSERT INTO favicon_bitmaps (icon_id, image_data, last_updated, width, "
      "height) VALUES (?, ?, ?, ?, ?)"));
  statement.BindInt64(0, icon_id);
  if (icon_data.get() && icon_data->size()) {
    statement.BindBlob(1, icon_data->front(),
                       static_cast<int>(icon_data->size()));
  } else {
    statement.BindNull(1);
  }
  statement.BindInt64(2, time.ToInternalValue());
  statement.BindInt(3, pixel_size.width());
  statement.BindInt(4, pixel_size.height());

  if (!statement.Run())
    return 0;
  return db_.GetLastInsertRowId();
}

bool ThumbnailDatabase::SetFaviconBitmap(
    FaviconBitmapID bitmap_id,
    scoped_refptr<base::RefCountedMemory> bitmap_data,
    base::Time time) {
  DCHECK(bitmap_id);
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "UPDATE favicon_bitmaps SET image_data=?, last_updated=? WHERE id=?"));
  if (bitmap_data.get() && bitmap_data->size()) {
    statement.BindBlob(0, bitmap_data->front(),
                       static_cast<int>(bitmap_data->size()));
  } else {
    statement.BindNull(0);
  }
  statement.BindInt64(1, time.ToInternalValue());
  statement.BindInt64(2, bitmap_id);

  return statement.Run();
}

bool ThumbnailDatabase::SetFaviconBitmapLastUpdateTime(
    FaviconBitmapID bitmap_id,
    base::Time time) {
  DCHECK(bitmap_id);
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "UPDATE favicon_bitmaps SET last_updated=? WHERE id=?"));
  statement.BindInt64(0, time.ToInternalValue());
  statement.BindInt64(1, bitmap_id);
  return statement.Run();
}

bool ThumbnailDatabase::DeleteFaviconBitmap(FaviconBitmapID bitmap_id) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "DELETE FROM favicon_bitmaps WHERE id=?"));
  statement.BindInt64(0, bitmap_id);
  return statement.Run();
}

bool ThumbnailDatabase::SetFaviconOutOfDate(chrome::FaviconID icon_id) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "UPDATE favicon_bitmaps SET last_updated=? WHERE icon_id=?"));
  statement.BindInt64(0, 0);
  statement.BindInt64(1, icon_id);

  return statement.Run();
}

chrome::FaviconID ThumbnailDatabase::GetFaviconIDForFaviconURL(
    const GURL& icon_url,
    int required_icon_type,
    chrome::IconType* icon_type) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "SELECT id, icon_type FROM favicons WHERE url=? AND (icon_type & ? > 0) "
      "ORDER BY icon_type DESC"));
  statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url));
  statement.BindInt(1, required_icon_type);

  if (!statement.Step())
    return 0;  // not cached

  if (icon_type)
    *icon_type = static_cast<chrome::IconType>(statement.ColumnInt(1));
  return statement.ColumnInt64(0);
}

bool ThumbnailDatabase::GetFaviconHeader(chrome::FaviconID icon_id,
                                         GURL* icon_url,
                                         chrome::IconType* icon_type) {
  DCHECK(icon_id);

  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "SELECT url, icon_type FROM favicons WHERE id=?"));
  statement.BindInt64(0, icon_id);

  if (!statement.Step())
    return false;  // No entry for the id.

  if (icon_url)
    *icon_url = GURL(statement.ColumnString(0));
  if (icon_type)
    *icon_type = static_cast<chrome::IconType>(statement.ColumnInt(1));

  return true;
}

chrome::FaviconID ThumbnailDatabase::AddFavicon(
    const GURL& icon_url,
    chrome::IconType icon_type) {

  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "INSERT INTO favicons (url, icon_type) VALUES (?, ?)"));
  statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url));
  statement.BindInt(1, icon_type);

  if (!statement.Run())
    return 0;
  return db_.GetLastInsertRowId();
}

chrome::FaviconID ThumbnailDatabase::AddFavicon(
    const GURL& icon_url,
    chrome::IconType icon_type,
    const scoped_refptr<base::RefCountedMemory>& icon_data,
    base::Time time,
    const gfx::Size& pixel_size) {
  chrome::FaviconID icon_id = AddFavicon(icon_url, icon_type);
  if (!icon_id || !AddFaviconBitmap(icon_id, icon_data, time, pixel_size))
    return 0;

  return icon_id;
}

bool ThumbnailDatabase::DeleteFavicon(chrome::FaviconID id) {
  sql::Statement statement;
  statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE,
      "DELETE FROM favicons WHERE id = ?"));
  statement.BindInt64(0, id);
  if (!statement.Run())
    return false;

  statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE,
      "DELETE FROM favicon_bitmaps WHERE icon_id = ?"));
  statement.BindInt64(0, id);
  return statement.Run();
}

bool ThumbnailDatabase::GetIconMappingsForPageURL(
    const GURL& page_url,
    int required_icon_types,
    std::vector<IconMapping>* filtered_mapping_data) {
  std::vector<IconMapping> mapping_data;
  if (!GetIconMappingsForPageURL(page_url, &mapping_data))
    return false;

  bool result = false;
  for (std::vector<IconMapping>::iterator m = mapping_data.begin();
       m != mapping_data.end(); ++m) {
    if (m->icon_type & required_icon_types) {
      result = true;
      if (!filtered_mapping_data)
        return result;

      // Restrict icon type of subsequent matches to |m->icon_type|.
      // |m->icon_type| is the largest IconType in |mapping_data| because
      // |mapping_data| is sorted in descending order of IconType.
      required_icon_types = m->icon_type;

      filtered_mapping_data->push_back(*m);
    }
  }
  return result;
}

bool ThumbnailDatabase::GetIconMappingsForPageURL(
    const GURL& page_url,
    std::vector<IconMapping>* mapping_data) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, "
      "favicons.url "
      "FROM icon_mapping "
      "INNER JOIN favicons "
      "ON icon_mapping.icon_id = favicons.id "
      "WHERE icon_mapping.page_url=? "
      "ORDER BY favicons.icon_type DESC"));
  statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url));

  bool result = false;
  while (statement.Step()) {
    result = true;
    if (!mapping_data)
      return result;

    IconMapping icon_mapping;
    FillIconMapping(statement, page_url, &icon_mapping);
    mapping_data->push_back(icon_mapping);
  }
  return result;
}

IconMappingID ThumbnailDatabase::AddIconMapping(const GURL& page_url,
                                                chrome::FaviconID icon_id) {
  const char kSql[] =
      "INSERT INTO icon_mapping (page_url, icon_id) VALUES (?, ?)";
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url));
  statement.BindInt64(1, icon_id);

  if (!statement.Run())
    return 0;

  return db_.GetLastInsertRowId();
}

bool ThumbnailDatabase::UpdateIconMapping(IconMappingID mapping_id,
                                          chrome::FaviconID icon_id) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "UPDATE icon_mapping SET icon_id=? WHERE id=?"));
  statement.BindInt64(0, icon_id);
  statement.BindInt64(1, mapping_id);

  return statement.Run();
}

bool ThumbnailDatabase::DeleteIconMappings(const GURL& page_url) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "DELETE FROM icon_mapping WHERE page_url = ?"));
  statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url));

  return statement.Run();
}

bool ThumbnailDatabase::DeleteIconMapping(IconMappingID mapping_id) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "DELETE FROM icon_mapping WHERE id=?"));
  statement.BindInt64(0, mapping_id);

  return statement.Run();
}

bool ThumbnailDatabase::HasMappingFor(chrome::FaviconID id) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "SELECT id FROM icon_mapping "
      "WHERE icon_id=?"));
  statement.BindInt64(0, id);

  return statement.Step();
}

bool ThumbnailDatabase::CloneIconMappings(const GURL& old_page_url,
                                          const GURL& new_page_url) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "SELECT icon_id FROM icon_mapping "
      "WHERE page_url=?"));
  if (!statement.is_valid())
    return false;

  // Do nothing if there are existing bindings
  statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url));
  if (statement.Step())
    return true;

  statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE,
      "INSERT INTO icon_mapping (page_url, icon_id) "
        "SELECT ?, icon_id FROM icon_mapping "
        "WHERE page_url = ?"));

  statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url));
  statement.BindString(1, URLDatabase::GURLToDatabaseURL(old_page_url));
  return statement.Run();
}

bool ThumbnailDatabase::InitIconMappingEnumerator(
    chrome::IconType type,
    IconMappingEnumerator* enumerator) {
  DCHECK(!enumerator->statement_.is_valid());
  enumerator->statement_.Assign(db_.GetCachedStatement(
      SQL_FROM_HERE,
      "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, "
             "favicons.url, icon_mapping.page_url "
         "FROM icon_mapping JOIN favicons ON ("
              "icon_mapping.icon_id = favicons.id) "
         "WHERE favicons.icon_type = ?"));
  enumerator->statement_.BindInt(0, type);
  return enumerator->statement_.is_valid();
}

bool ThumbnailDatabase::RetainDataForPageUrls(
    const std::vector<GURL>& urls_to_keep) {
  sql::Transaction transaction(&db_);
  if (!transaction.Begin())
    return false;

  // temp.icon_id_mapping generates new icon ids as consecutive
  // integers starting from 1, and maps them to the old icon ids.
  {
    const char kIconMappingCreate[] =
        "CREATE TEMP TABLE icon_id_mapping "
        "("
        "new_icon_id INTEGER PRIMARY KEY,"
        "old_icon_id INTEGER NOT NULL UNIQUE"
        ")";
    if (!db_.Execute(kIconMappingCreate))
      return false;

    // Insert the icon ids for retained urls, skipping duplicates.
    const char kIconMappingSql[] =
        "INSERT OR IGNORE INTO temp.icon_id_mapping (old_icon_id) "
        "SELECT icon_id FROM icon_mapping WHERE page_url = ?";
    sql::Statement statement(db_.GetUniqueStatement(kIconMappingSql));
    for (std::vector<GURL>::const_iterator
             i = urls_to_keep.begin(); i != urls_to_keep.end(); ++i) {
      statement.BindString(0, URLDatabase::GURLToDatabaseURL(*i));
      if (!statement.Run())
        return false;
      statement.Reset(true);
    }
  }

  const char kRenameIconMappingTable[] =
      "ALTER TABLE icon_mapping RENAME TO old_icon_mapping";
  const char kCopyIconMapping[] =
      "INSERT INTO icon_mapping (page_url, icon_id) "
      "SELECT old.page_url, mapping.new_icon_id "
      "FROM old_icon_mapping AS old "
      "JOIN temp.icon_id_mapping AS mapping "
      "ON (old.icon_id = mapping.old_icon_id)";
  const char kDropOldIconMappingTable[] = "DROP TABLE old_icon_mapping";

  const char kRenameFaviconsTable[] =
      "ALTER TABLE favicons RENAME TO old_favicons";
  const char kCopyFavicons[] =
      "INSERT INTO favicons (id, url, icon_type) "
      "SELECT mapping.new_icon_id, old.url, old.icon_type "
      "FROM old_favicons AS old "
      "JOIN temp.icon_id_mapping AS mapping "
      "ON (old.id = mapping.old_icon_id)";
  const char kDropOldFaviconsTable[] = "DROP TABLE old_favicons";

  const char kRenameFaviconBitmapsTable[] =
      "ALTER TABLE favicon_bitmaps RENAME TO old_favicon_bitmaps";
  const char kCopyFaviconBitmaps[] =
      "INSERT INTO favicon_bitmaps "
      "  (icon_id, last_updated, image_data, width, height) "
      "SELECT mapping.new_icon_id, old.last_updated, "
      "    old.image_data, old.width, old.height "
      "FROM old_favicon_bitmaps AS old "
      "JOIN temp.icon_id_mapping AS mapping "
      "ON (old.icon_id = mapping.old_icon_id)";
  const char kDropOldFaviconBitmapsTable[] =
      "DROP TABLE old_favicon_bitmaps";

  // Rename existing tables to new location.
  if (!db_.Execute(kRenameIconMappingTable) ||
      !db_.Execute(kRenameFaviconsTable) ||
      !db_.Execute(kRenameFaviconBitmapsTable)) {
    return false;
  }

  // Initialize the replacement tables.  At this point the old indices
  // still exist (pointing to the old_* tables), so do not initialize
  // the indices.
  if (!InitTables(&db_))
    return false;

  // Copy all of the data over.
  if (!db_.Execute(kCopyIconMapping) ||
      !db_.Execute(kCopyFavicons) ||
      !db_.Execute(kCopyFaviconBitmaps)) {
    return false;
  }

  // Drop the old_* tables, which also drops the indices.
  if (!db_.Execute(kDropOldIconMappingTable) ||
      !db_.Execute(kDropOldFaviconsTable) ||
      !db_.Execute(kDropOldFaviconBitmapsTable)) {
    return false;
  }

  // Recreate the indices.
  // TODO(shess): UNIQUE indices could fail due to duplication.  This
  // could happen in case of corruption.
  if (!InitIndices(&db_))
    return false;

  const char kIconMappingDrop[] = "DROP TABLE temp.icon_id_mapping";
  if (!db_.Execute(kIconMappingDrop))
    return false;

  return transaction.Commit();
}

sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db,
                                                const base::FilePath& db_name) {
  size_t startup_kb = 0;
  int64 size_64;
  if (base::GetFileSize(db_name, &size_64))
    startup_kb = static_cast<size_t>(size_64 / 1024);

  db->set_histogram_tag("Thumbnail");
  db->set_error_callback(base::Bind(&DatabaseErrorCallback,
                                    db, db_name, startup_kb));

  // Thumbnails db now only stores favicons, so we don't need that big a page
  // size or cache.
  db->set_page_size(2048);
  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;

  return sql::INIT_OK;
}

sql::InitStatus ThumbnailDatabase::InitImpl(const base::FilePath& db_name) {
  sql::InitStatus status = OpenDatabase(&db_, db_name);
  if (status != sql::INIT_OK)
    return status;

  // Clear databases which are too old to process.
  DCHECK_LT(kDeprecatedVersionNumber, kCurrentVersionNumber);
  sql::MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersionNumber);

  // TODO(shess): Sqlite.Version.Thumbnail shows versions 22, 23, and
  // 25.  Future versions are not destroyed because that could lead to
  // data loss if the profile is opened by a later channel, but
  // perhaps a heuristic like >kCurrentVersionNumber+3 could be used.

  // Scope initialization in a transaction so we can't be partially initialized.
  sql::Transaction transaction(&db_);
  if (!transaction.Begin())
    return sql::INIT_FAILURE;

  // TODO(shess): Failing Begin() implies that something serious is
  // wrong with the database.  Raze() may be in order.

#if defined(OS_MACOSX)
  // Exclude the thumbnails file from backups.
  base::mac::SetFileBackupExclusion(db_name);
#endif

  // thumbnails table has been obsolete for a long time, remove any
  // detrious.
  ignore_result(db_.Execute("DROP TABLE IF EXISTS thumbnails"));

  // At some point, operations involving temporary tables weren't done
  // atomically and users have been stranded.  Drop those tables and
  // move on.
  // TODO(shess): Prove it?  Audit all cases and see if it's possible
  // that this implies non-atomic update, and should thus be handled
  // via the corruption handler.
  ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_favicons"));
  ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_favicon_bitmaps"));
  ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_icon_mapping"));

  // Create the tables.
  if (!meta_table_.Init(&db_, kCurrentVersionNumber,
                        kCompatibleVersionNumber) ||
      !InitTables(&db_) ||
      !InitIndices(&db_)) {
    return sql::INIT_FAILURE;
  }

  // Version check. We should not encounter a database too old for us to handle
  // in the wild, so we try to continue in that case.
  if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
    LOG(WARNING) << "Thumbnail database is too new.";
    return sql::INIT_TOO_NEW;
  }

  int cur_version = meta_table_.GetVersionNumber();

  if (!db_.DoesColumnExist("favicons", "icon_type")) {
    LOG(ERROR) << "Raze because of missing favicon.icon_type";
    RecordInvalidStructure(STRUCTURE_EVENT_VERSION4);

    db_.RazeAndClose();
    return sql::INIT_FAILURE;
  }

  if (cur_version < 7 && !db_.DoesColumnExist("favicons", "sizes")) {
    LOG(ERROR) << "Raze because of missing favicon.sizes";
    RecordInvalidStructure(STRUCTURE_EVENT_VERSION5);

    db_.RazeAndClose();
    return sql::INIT_FAILURE;
  }

  if (cur_version == 5) {
    ++cur_version;
    if (!UpgradeToVersion6())
      return CantUpgradeToVersion(cur_version);
  }

  if (cur_version == 6) {
    ++cur_version;
    if (!UpgradeToVersion7())
      return CantUpgradeToVersion(cur_version);
  }

  LOG_IF(WARNING, cur_version < kCurrentVersionNumber) <<
      "Thumbnail database version " << cur_version << " is too old to handle.";

  // Initialization is complete.
  if (!transaction.Commit())
    return sql::INIT_FAILURE;

  // Raze the database if the structure of the favicons database is not what
  // it should be. This error cannot be detected via the SQL error code because
  // the error code for running SQL statements against a database with missing
  // columns is SQLITE_ERROR which is not unique enough to act upon.
  // TODO(pkotwicz): Revisit this in M27 and see if the razing can be removed.
  // (crbug.com/166453)
  if (IsFaviconDBStructureIncorrect()) {
    LOG(ERROR) << "Raze because of invalid favicon db structure.";
    RecordInvalidStructure(STRUCTURE_EVENT_FAVICON);

    db_.RazeAndClose();
    return sql::INIT_FAILURE;
  }

  return sql::INIT_OK;
}

sql::InitStatus ThumbnailDatabase::CantUpgradeToVersion(int cur_version) {
  LOG(WARNING) << "Unable to update to thumbnail database to version " <<
               cur_version << ".";
  db_.Close();
  return sql::INIT_FAILURE;
}

bool ThumbnailDatabase::UpgradeToVersion6() {
  // Move bitmap data from favicons to favicon_bitmaps.
  bool success =
      db_.Execute("INSERT INTO favicon_bitmaps (icon_id, last_updated, "
                  "image_data, width, height)"
                  "SELECT id, last_updated, image_data, 0, 0 FROM favicons") &&
      db_.Execute("CREATE TABLE temp_favicons ("
                  "id INTEGER PRIMARY KEY,"
                  "url LONGVARCHAR NOT NULL,"
                  "icon_type INTEGER DEFAULT 1,"
                  // default icon_type FAVICON to be consistent with
                  // past migration.
                  "sizes LONGVARCHAR)") &&
      db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) "
                  "SELECT id, url, icon_type FROM favicons") &&
      db_.Execute("DROP TABLE favicons") &&
      db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons");
  // NOTE(shess): v7 will re-create the index.
  if (!success)
    return false;

  meta_table_.SetVersionNumber(6);
  meta_table_.SetCompatibleVersionNumber(std::min(6, kCompatibleVersionNumber));
  return true;
}

bool ThumbnailDatabase::UpgradeToVersion7() {
  // Sizes column was never used, remove it.
  bool success =
      db_.Execute("CREATE TABLE temp_favicons ("
                  "id INTEGER PRIMARY KEY,"
                  "url LONGVARCHAR NOT NULL,"
                  // default icon_type FAVICON to be consistent with
                  // past migration.
                  "icon_type INTEGER DEFAULT 1)") &&
      db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) "
                  "SELECT id, url, icon_type FROM favicons") &&
      db_.Execute("DROP TABLE favicons") &&
      db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons") &&
      db_.Execute("CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)");

  if (!success)
    return false;

  meta_table_.SetVersionNumber(7);
  meta_table_.SetCompatibleVersionNumber(std::min(7, kCompatibleVersionNumber));
  return true;
}

bool ThumbnailDatabase::IsFaviconDBStructureIncorrect() {
  return !db_.IsSQLValid("SELECT id, url, icon_type FROM favicons");
}

}  // namespace history