// Copyright (c) 2011 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 "app/sql/statement.h"
#include "app/sql/transaction.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/memory/ref_counted_memory.h"
#include "base/time.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/diagnostics/sqlite_diagnostics.h"
#include "chrome/browser/history/history_publisher.h"
#include "chrome/browser/history/top_sites.h"
#include "chrome/browser/history/url_database.h"
#include "chrome/common/thumbnail_score.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/jpeg_codec.h"

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

static 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<history::IconType>(statement.ColumnInt(2));
  icon_mapping->page_url = page_url;
}

namespace history {

// Version number of the database.
static const int kCurrentVersionNumber = 4;
static const int kCompatibleVersionNumber = 4;

ThumbnailDatabase::ThumbnailDatabase()
    : history_publisher_(NULL),
      use_top_sites_(false) {
}

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

sql::InitStatus ThumbnailDatabase::Init(
    const FilePath& db_name,
    const HistoryPublisher* history_publisher,
    URLDatabase* url_db) {
  history_publisher_ = history_publisher;
  sql::InitStatus status = OpenDatabase(&db_, db_name);
  if (status != sql::INIT_OK)
    return status;

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

#if defined(OS_MACOSX)
  // Exclude the thumbnails file and its journal from backups.
  base::mac::SetFileBackupExclusion(db_name, true);
  FilePath::StringType db_name_string(db_name.value());
  db_name_string += "-journal";
  FilePath db_journal_name(db_name_string);
  base::mac::SetFileBackupExclusion(db_journal_name, true);
#endif

  // Create the tables.
  if (!meta_table_.Init(&db_, kCurrentVersionNumber,
                        kCompatibleVersionNumber) ||
      !InitThumbnailTable() ||
      !InitFaviconsTable(&db_, false) ||
      !InitIconMappingTable(&db_, false)) {
    db_.Close();
    return sql::INIT_FAILURE;
  }
  InitFaviconsIndex();
  InitIconMappingIndex();

  // 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 (cur_version == 2) {
    if (!UpgradeToVersion3()) {
      LOG(WARNING) << "Unable to update to thumbnail database to version 3.";
      db_.Close();
      return sql::INIT_FAILURE;
    }
    ++cur_version;
  }

  if (cur_version == 3) {
    if (!UpgradeToVersion4() || !MigrateIconMappingData(url_db)) {
      LOG(WARNING) << "Unable to update to thumbnail database to version 4.";
      db_.Close();
      return sql::INIT_FAILURE;
    }
    ++cur_version;
  }

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

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

  return sql::INIT_OK;
}

sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db,
                                                const FilePath& db_name) {
  // Set the exceptional sqlite error handler.
  db->set_error_delegate(GetErrorHandlerForThumbnailDb());

  // 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;
}

bool ThumbnailDatabase::InitThumbnailTable() {
  if (!db_.DoesTableExist("thumbnails")) {
    use_top_sites_ = true;
  }
  return true;
}

bool ThumbnailDatabase::UpgradeToVersion3() {
  if (use_top_sites_) {
    meta_table_.SetVersionNumber(3);
    meta_table_.SetCompatibleVersionNumber(
        std::min(3, kCompatibleVersionNumber));
    return true;  // Not needed after migration to TopSites.
  }

  // sqlite doesn't like the "ALTER TABLE xxx ADD (column_one, two,
  // three)" syntax, so list out the commands we need to execute:
  const char* alterations[] = {
    "ALTER TABLE thumbnails ADD boring_score DOUBLE DEFAULT 1.0",
    "ALTER TABLE thumbnails ADD good_clipping INTEGER DEFAULT 0",
    "ALTER TABLE thumbnails ADD at_top INTEGER DEFAULT 0",
    "ALTER TABLE thumbnails ADD last_updated INTEGER DEFAULT 0",
    NULL
  };

  for (int i = 0; alterations[i] != NULL; ++i) {
    if (!db_.Execute(alterations[i])) {
      NOTREACHED();
      return false;
    }
  }

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

bool ThumbnailDatabase::RecreateThumbnailTable() {
  if (use_top_sites_)
    return true;  // Not needed after migration to TopSites.

  if (!db_.Execute("DROP TABLE thumbnails"))
    return false;
  return InitThumbnailTable();
}

bool ThumbnailDatabase::InitFaviconsTable(sql::Connection* db,
                                          bool is_temporary) {
  // Note: if you update the schema, don't forget to update
  // CopyToTemporaryFaviconTable as well.
  const char* name = is_temporary ? "temp_favicons" : "favicons";
  if (!db->DoesTableExist(name)) {
    std::string sql;
    sql.append("CREATE TABLE ");
    sql.append(name);
    sql.append("("
               "id INTEGER PRIMARY KEY,"
               "url LONGVARCHAR NOT NULL,"
               "last_updated INTEGER DEFAULT 0,"
               "image_data BLOB,"
               "icon_type INTEGER DEFAULT 1)"); // Set the default as FAVICON
                                                // to be consistent with table
                                                // upgrade in
                                                // UpgradeToVersion4().
    if (!db->Execute(sql.c_str()))
      return false;
  }
  return true;
}

void ThumbnailDatabase::InitFaviconsIndex() {
  // Add an index on the url column. We ignore errors. Since this is always
  // called during startup, the index will normally already exist.
  db_.Execute("CREATE INDEX favicons_url ON favicons(url)");
}

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

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

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

void ThumbnailDatabase::SetPageThumbnail(
    const GURL& url,
    URLID id,
    const SkBitmap& thumbnail,
    const ThumbnailScore& score,
    base::Time time) {
  if (use_top_sites_) {
    LOG(WARNING) << "Use TopSites instead.";
    return;  // Not possible after migration to TopSites.
  }

  if (!thumbnail.isNull()) {
    bool add_thumbnail = true;
    ThumbnailScore current_score;
    if (ThumbnailScoreForId(id, &current_score)) {
      add_thumbnail = ShouldReplaceThumbnailWith(current_score, score);
    }

    if (add_thumbnail) {
      sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
          "INSERT OR REPLACE INTO thumbnails "
          "(url_id, boring_score, good_clipping, at_top, last_updated, data) "
          "VALUES (?,?,?,?,?,?)"));
      if (!statement)
        return;

      // We use 90 quality (out of 100) which is pretty high, because
      // we're very sensitive to artifacts for these small sized,
      // highly detailed images.
      std::vector<unsigned char> jpeg_data;
      SkAutoLockPixels thumbnail_lock(thumbnail);
      bool encoded = gfx::JPEGCodec::Encode(
          reinterpret_cast<unsigned char*>(thumbnail.getAddr32(0, 0)),
          gfx::JPEGCodec::FORMAT_SkBitmap, thumbnail.width(),
          thumbnail.height(),
          static_cast<int>(thumbnail.rowBytes()), 90,
          &jpeg_data);

      if (encoded) {
        statement.BindInt64(0, id);
        statement.BindDouble(1, score.boring_score);
        statement.BindBool(2, score.good_clipping);
        statement.BindBool(3, score.at_top);
        statement.BindInt64(4, score.time_at_snapshot.ToTimeT());
        statement.BindBlob(5, &jpeg_data[0],
                           static_cast<int>(jpeg_data.size()));
        if (!statement.Run())
          NOTREACHED() << db_.GetErrorMessage();
      }

      // Publish the thumbnail to any indexers listening to us.
      // The tests may send an invalid url. Hence avoid publishing those.
      if (url.is_valid() && history_publisher_ != NULL)
        history_publisher_->PublishPageThumbnail(jpeg_data, url, time);
    }
  } else {
    if (!DeleteThumbnail(id) )
      DLOG(WARNING) << "Unable to delete thumbnail";
  }
}

bool ThumbnailDatabase::GetPageThumbnail(URLID id,
                                         std::vector<unsigned char>* data) {
  if (use_top_sites_) {
    LOG(WARNING) << "Use TopSites instead.";
    return false;  // Not possible after migration to TopSites.
  }

  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "SELECT data FROM thumbnails WHERE url_id=?"));
  if (!statement)
    return false;

  statement.BindInt64(0, id);
  if (!statement.Step())
    return false;  // don't have a thumbnail for this ID

  statement.ColumnBlobAsVector(0, data);
  return true;
}

bool ThumbnailDatabase::DeleteThumbnail(URLID id) {
  if (use_top_sites_) {
    return true;  // Not possible after migration to TopSites.
  }

  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "DELETE FROM thumbnails WHERE url_id = ?"));
  if (!statement)
    return false;

  statement.BindInt64(0, id);
  return statement.Run();
}

bool ThumbnailDatabase::ThumbnailScoreForId(URLID id,
                                            ThumbnailScore* score) {
  if (use_top_sites_) {
    LOG(WARNING) << "Use TopSites instead.";
    return false;  // Not possible after migration to TopSites.
  }

  // Fetch the current thumbnail's information to make sure we
  // aren't replacing a good thumbnail with one that's worse.
  sql::Statement select_statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "SELECT boring_score, good_clipping, at_top, last_updated "
      "FROM thumbnails WHERE url_id=?"));
  if (!select_statement) {
    NOTREACHED() << "Couldn't build select statement!";
  } else {
    select_statement.BindInt64(0, id);
    if (select_statement.Step()) {
      double current_boring_score = select_statement.ColumnDouble(0);
      bool current_clipping = select_statement.ColumnBool(1);
      bool current_at_top = select_statement.ColumnBool(2);
      base::Time last_updated =
          base::Time::FromTimeT(select_statement.ColumnInt64(3));
      *score = ThumbnailScore(current_boring_score, current_clipping,
                              current_at_top, last_updated);
      return true;
    }
  }

  return false;
}

bool ThumbnailDatabase::SetFavicon(URLID icon_id,
                                   scoped_refptr<RefCountedMemory> icon_data,
                                   base::Time time) {
  DCHECK(icon_id);
  if (icon_data->size()) {
    sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
        "UPDATE favicons SET image_data=?, last_updated=? WHERE id=?"));
    if (!statement)
      return 0;

    statement.BindBlob(0, icon_data->front(),
                       static_cast<int>(icon_data->size()));
    statement.BindInt64(1, time.ToTimeT());
    statement.BindInt64(2, icon_id);
    return statement.Run();
  } else {
    sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
        "UPDATE favicons SET image_data=NULL, last_updated=? WHERE id=?"));
    if (!statement)
      return 0;

    statement.BindInt64(0, time.ToTimeT());
    statement.BindInt64(1, icon_id);
    return statement.Run();
  }
}

bool ThumbnailDatabase::SetFaviconLastUpdateTime(FaviconID icon_id,
                                                 base::Time time) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "UPDATE favicons SET last_updated=? WHERE id=?"));
  if (!statement)
    return 0;

  statement.BindInt64(0, time.ToTimeT());
  statement.BindInt64(1, icon_id);
  return statement.Run();
}

FaviconID ThumbnailDatabase::GetFaviconIDForFaviconURL(const GURL& icon_url,
                                                       int required_icon_type,
                                                       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"));
  if (!statement)
    return 0;

  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<IconType>(statement.ColumnInt(1));
  return statement.ColumnInt64(0);
}

bool ThumbnailDatabase::GetFavicon(
    FaviconID icon_id,
    base::Time* last_updated,
    std::vector<unsigned char>* png_icon_data,
    GURL* icon_url) {
  DCHECK(icon_id);

  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "SELECT last_updated, image_data, url FROM favicons WHERE id=?"));
  if (!statement)
    return 0;

  statement.BindInt64(0, icon_id);

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

  *last_updated = base::Time::FromTimeT(statement.ColumnInt64(0));
  if (statement.ColumnByteLength(1) > 0)
    statement.ColumnBlobAsVector(1, png_icon_data);
  if (icon_url)
    *icon_url = GURL(statement.ColumnString(2));

  return true;
}

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

  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "INSERT INTO favicons (url, icon_type) VALUES (?, ?)"));
  if (!statement)
    return 0;

  statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url));
  statement.BindInt(1, icon_type);
  if (!statement.Run())
    return 0;
  return db_.GetLastInsertRowId();
}

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

  statement.BindInt64(0, id);
  return statement.Run();
}

bool ThumbnailDatabase::GetIconMappingForPageURL(const GURL& page_url,
                                                 IconType required_icon_type,
                                                 IconMapping* icon_mapping) {
  std::vector<IconMapping> icon_mappings;
  if (!GetIconMappingsForPageURL(page_url, &icon_mappings))
    return false;

  for (std::vector<IconMapping>::iterator m = icon_mappings.begin();
      m != icon_mappings.end(); ++m) {
    if (m->icon_type == required_icon_type) {
      if (icon_mapping != NULL)
        *icon_mapping = *m;
      return true;
    }
  }

  return false;
}

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 "
      "FROM icon_mapping "
      "INNER JOIN favicons "
      "ON icon_mapping.icon_id = favicons.id "
      "WHERE icon_mapping.page_url=? "
      "ORDER BY favicons.icon_type DESC"));
  if (!statement)
    return false;

  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,
                                                FaviconID icon_id) {
  return AddIconMapping(page_url, icon_id, false);
}

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

  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 = ?"));
  if (!statement)
    return false;

  statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url));
  return statement.Run();
}

bool ThumbnailDatabase::HasMappingFor(FaviconID id) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "SELECT id FROM icon_mapping "
      "WHERE icon_id=?"));
  if (!statement)
    return false;

  statement.BindInt64(0, id);
  return statement.Step();
}

bool ThumbnailDatabase::MigrateIconMappingData(URLDatabase* url_db) {
  URLDatabase::IconMappingEnumerator e;
  if (!url_db->InitIconMappingEnumeratorForEverything(&e))
    return false;

  IconMapping info;
  while (e.GetNextIconMapping(&info)) {
    // TODO: Using bulk insert to improve the performance.
    if (!AddIconMapping(info.page_url, info.icon_id))
      return false;
  }
  return true;
}

IconMappingID ThumbnailDatabase::AddToTemporaryIconMappingTable(
    const GURL& page_url, const FaviconID icon_id) {
  return AddIconMapping(page_url, icon_id, true);
}

bool ThumbnailDatabase::CommitTemporaryIconMappingTable() {
  // Delete the old icon_mapping table.
  if (!db_.Execute("DROP TABLE icon_mapping"))
    return false;

  // Rename the temporary one.
  if (!db_.Execute("ALTER TABLE temp_icon_mapping RENAME TO icon_mapping"))
    return false;

  // The renamed table needs the index (the temporary table doesn't have one).
  InitIconMappingIndex();

  return true;
}

FaviconID ThumbnailDatabase::CopyToTemporaryFaviconTable(FaviconID source) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
      "INSERT INTO temp_favicons (url, last_updated, image_data, icon_type)"
      "SELECT url, last_updated, image_data, icon_type "
      "FROM favicons WHERE id = ?"));
  if (!statement)
    return 0;
  statement.BindInt64(0, source);
  if (!statement.Run())
    return 0;

  // We return the ID of the newly inserted favicon.
  return db_.GetLastInsertRowId();
}

bool ThumbnailDatabase::CommitTemporaryFaviconTable() {
  // Delete the old favicons table.
  if (!db_.Execute("DROP TABLE favicons"))
    return false;

  // Rename the temporary one.
  if (!db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons"))
    return false;

  // The renamed table needs the index (the temporary table doesn't have one).
  InitFaviconsIndex();
  return true;
}

bool ThumbnailDatabase::NeedsMigrationToTopSites() {
  return !use_top_sites_;
}

bool ThumbnailDatabase::RenameAndDropThumbnails(const FilePath& old_db_file,
                                                const FilePath& new_db_file) {
  // Init favicons table - same schema as the thumbnails.
  sql::Connection favicons;
  if (OpenDatabase(&favicons, new_db_file) != sql::INIT_OK)
    return false;

  if (!InitFaviconsTable(&favicons, false) ||
      !InitIconMappingTable(&favicons, false)) {
    NOTREACHED() << "Couldn't init favicons and icon-mapping table.";
    favicons.Close();
    return false;
  }
  favicons.Close();

  // Can't attach within a transaction.
  if (transaction_nesting())
    CommitTransaction();

  // Attach new DB.
  {
    // This block is needed because otherwise the attach statement is
    // never cleared from cache and we can't close the DB :P
    sql::Statement attach(db_.GetUniqueStatement("ATTACH ? AS new_favicons"));
    if (!attach) {
      NOTREACHED() << "Unable to attach database.";
      // Keep the transaction open, even though we failed.
      BeginTransaction();
      return false;
    }

#if defined(OS_POSIX)
    attach.BindString(0, new_db_file.value());
#else
    attach.BindString(0, WideToUTF8(new_db_file.value()));
#endif

    if (!attach.Run()) {
      NOTREACHED() << db_.GetErrorMessage();
      BeginTransaction();
      return false;
    }
  }

  // Move favicons to the new DB.
  if (!db_.Execute("INSERT OR REPLACE INTO new_favicons.favicons "
                   "SELECT * FROM favicons")) {
    NOTREACHED() << "Unable to copy favicons.";
    BeginTransaction();
    return false;
  }

  if (!db_.Execute("DETACH new_favicons")) {
    NOTREACHED() << "Unable to detach database.";
    BeginTransaction();
    return false;
  }

  db_.Close();

  // Reset the DB to point to new file.
  if (OpenDatabase(&db_, new_db_file) != sql::INIT_OK)
    return false;

  file_util::Delete(old_db_file, false);

  InitFaviconsIndex();

  // Reopen the transaction.
  BeginTransaction();
  use_top_sites_ = true;
  return true;
}

bool ThumbnailDatabase::InitIconMappingTable(sql::Connection* db,
                                             bool is_temporary) {
  const char* name = is_temporary ? "temp_icon_mapping" : "icon_mapping";
  if (!db->DoesTableExist(name)) {
    std::string sql;
    sql.append("CREATE TABLE ");
    sql.append(name);
    sql.append("("
               "id INTEGER PRIMARY KEY,"
               "page_url LONGVARCHAR NOT NULL,"
               "icon_id INTEGER)");
    if (!db->Execute(sql.c_str()))
      return false;
  }
  return true;
}

void ThumbnailDatabase::InitIconMappingIndex() {
  // Add an index on the url column. We ignore errors. Since this is always
  // called during startup, the index will normally already exist.
  db_.Execute("CREATE INDEX icon_mapping_page_url_idx"
              " ON icon_mapping(page_url)");
  db_.Execute("CREATE INDEX icon_mapping_icon_id_idx ON icon_mapping(icon_id)");
}

IconMappingID ThumbnailDatabase::AddIconMapping(const GURL& page_url,
                                                FaviconID icon_id,
                                                bool is_temporary) {
  const char* name = is_temporary ? "temp_icon_mapping" : "icon_mapping";
  const char* statement_name =
      is_temporary ? "add_temp_icon_mapping" : "add_icon_mapping";

  std::string sql;
  sql.append("INSERT INTO ");
  sql.append(name);
  sql.append("(page_url, icon_id) VALUES (?, ?)");

  sql::Statement statement(
      db_.GetCachedStatement(sql::StatementID(statement_name), sql.c_str()));
  if (!statement)
    return 0;

  statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url));
  statement.BindInt64(1, icon_id);

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

  return db_.GetLastInsertRowId();
}

bool ThumbnailDatabase::UpgradeToVersion4() {
  // Set the default icon type as favicon, so the current data are set
  // correctly.
  if (!db_.Execute("ALTER TABLE favicons ADD icon_type INTEGER DEFAULT 1")) {
    NOTREACHED();
    return false;
  }
  meta_table_.SetVersionNumber(4);
  meta_table_.SetCompatibleVersionNumber(std::min(4, kCompatibleVersionNumber));
  return true;
}

}  // namespace history