// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/favicon/core/favicon_database.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <bit>
#include <string>
#include <tuple>
#include <utility>

#include "base/debug/alias.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted_memory.h"
#include "base/metrics/histogram_macros.h"
#include "base/rand_util.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/database_utils/upper_bound_string.h"
#include "components/database_utils/url_converter.h"
#include "components/favicon/core/favicon_types.h"
#include "components/favicon_base/favicon_types.h"
#include "sql/recovery.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "url/origin.h"

#if BUILDFLAG(IS_APPLE)
#include "base/apple/backup_util.h"
#endif

namespace favicon {

// 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.
//   page_url_type    The type of the `page_url`. This is computed when the
//                    entry is added. This may differ from the current type of
//                    the `page_url`. For example if a site moves, or sign-in
//                    state changes, the current page_url_type of the page may
//                    differ from what is stored in the row. See `PageUrlType`
//                    for valid values. By default this is `kRegularPage`.
//
// 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 kFavicon 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. Value 0 denotes that the bitmap
//                    has been explicitly expired.
//                    This is used only for ON_VISIT icons, for ON_DEMAND the
//                    value is always 0.
//   image_data       PNG encoded data of the favicon.
//   width            Pixel width of `image_data`.
//   height           Pixel height of `image_data`.
//   last_requested   The time at which this bitmap was last requested. This
//                    entry is non-zero iff the bitmap is of type ON_DEMAND.
//                    This info is used for clearing old ON_DEMAND bitmaps.
//                    (On-demand bitmaps cannot get cleared along with expired
//                    visits in history DB because there is no corresponding
//                    visit.)

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).

// TODO(ckitagawa): Add commit hash after landing.
// Version 9: <TODO>/r6208170 by ckitagawa@chromium.org on 2025-01-28
// Version 8: 982ef2c1/r323176 by rogerm@chromium.org on 2015-03-31
// Version 7: 911a634d/r209424 by qsr@chromium.org on 2013-07-01 (depr.)
// Version 6: 610f923b/r152367 by pkotwicz@chromium.org on 2012-08-20 (depr.)
// Version 5: e2ee8ae9/r105004 by groby@chromium.org on 2011-10-12 (deprecated)
// 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 = 9;
const int kCompatibleVersionNumber = 9;
const int kDeprecatedVersionNumber = 7;  // and earlier.

// NOTE(shess): Schema modifications must consider initial creation in
// `InitImpl()` and history pruning in `RetainDataForPageUrls()`.
bool InitTables(sql::Database* db) {
  static const char kIconMappingSql[] =
      "CREATE TABLE IF NOT EXISTS icon_mapping"
      "("
      "id INTEGER PRIMARY KEY,"
      "page_url LONGVARCHAR NOT NULL,"
      "icon_id INTEGER,"
      "page_url_type INTEGER DEFAULT 0"
      ")";
  if (!db->Execute(kIconMappingSql))
    return false;

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

  static 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,"
      // This field is at the end so that fresh tables and migrated tables have
      // the same layout.
      "last_requested INTEGER DEFAULT 0"
      ")";
  if (!db->Execute(kFaviconBitmapsSql))
    return false;

  return true;
}

// NOTE(shess): Schema modifications must consider initial creation in
// `InitImpl()` and history pruning in `RetainDataForPageUrls()`.
bool InitIndices(sql::Database* db) {
  static const char kIconMappingUrlIndexSql[] =
      "CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx"
      " ON icon_mapping(page_url)";
  static 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;
  }

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

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

void DatabaseErrorCallback(sql::Database* db,
                           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.

  // Attempt to recover a corrupt database, if it is eligible to be recovered.
  if (sql::Recovery::RecoverIfPossible(
          db, extended_error,
          sql::Recovery::Strategy::kRecoverWithMetaVersionOrRaze)) {
    // Recovery was attempted. The database handle has been poisoned and the
    // error callback has been reset.

    // 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.

    // Signal the test-expectation framework that the error was handled.
    std::ignore = sql::Database::IsExpectedSqliteError(extended_error);
    return;
  }

  // The default handling is to log an error on debug and to ignore on release.
  if (!sql::Database::IsExpectedSqliteError(extended_error)) {
    DLOG(ERROR) << db->GetErrorMessage();
  }
}

}  // namespace

FaviconDatabase::IconMappingEnumerator::IconMappingEnumerator() = default;

FaviconDatabase::IconMappingEnumerator::~IconMappingEnumerator() = default;

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

FaviconDatabase::FaviconDatabase()
    : db_(sql::DatabaseOptions()
              // Favicons db only stores favicons, so we don't need that big a
              // page size or cache.
              .set_page_size(2048)
              .set_cache_size(32),
          /*tag=*/"Thumbnail") {}

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

sql::InitStatus FaviconDatabase::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 RazeAndPoison() 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 FaviconDatabase::ComputeDatabaseMetrics() {
  // Calculate the size of the favicon database.
  sql::Statement page_count(
      db_.GetCachedStatement(SQL_FROM_HERE, "PRAGMA page_count"));
  int64_t page_count_bytes = page_count.Step() ? page_count.ColumnInt64(0) : 0;
  sql::Statement page_size(
      db_.GetCachedStatement(SQL_FROM_HERE, "PRAGMA page_size"));
  int64_t page_size_bytes = page_size.Step() ? page_size.ColumnInt64(0) : 0;
  int size_mb =
      static_cast<int>((page_count_bytes * page_size_bytes) / (1024 * 1024));
  UMA_HISTOGRAM_MEMORY_MB("History.FaviconDatabaseSizeMB", size_mb);
}

void FaviconDatabase::BeginTransaction() {
  db_.BeginTransactionDeprecated();
}

void FaviconDatabase::CommitTransaction() {
  db_.CommitTransactionDeprecated();
}

void FaviconDatabase::RollbackTransaction() {
  db_.RollbackTransactionDeprecated();
}

void FaviconDatabase::Vacuum() {
  DCHECK(!db_.HasActiveTransactions())
      << "Can not have a transaction when vacuuming.";
  std::ignore = db_.Execute("VACUUM");
}

std::map<favicon_base::FaviconID, IconMappingsForExpiry>
FaviconDatabase::GetOldOnDemandFavicons(base::Time threshold) {
  // Restrict to on-demand bitmaps (i.e. with last_requested != 0).
  // This is called rarely during history expiration cleanup and hence not worth
  // caching.
  sql::Statement old_icons(db_.GetUniqueStatement(
      "SELECT favicons.id, favicons.url, icon_mapping.page_url "
      "FROM favicons "
      "JOIN favicon_bitmaps ON (favicon_bitmaps.icon_id = favicons.id) "
      "JOIN icon_mapping ON (icon_mapping.icon_id = favicon_bitmaps.icon_id) "
      "WHERE (favicon_bitmaps.last_requested > 0 AND "
      "       favicon_bitmaps.last_requested < ?)"));
  old_icons.BindTime(0, threshold);

  std::map<favicon_base::FaviconID, IconMappingsForExpiry> icon_mappings;

  while (old_icons.Step()) {
    favicon_base::FaviconID id = old_icons.ColumnInt64(0);
    icon_mappings[id].icon_url = GURL(old_icons.ColumnStringView(1));
    icon_mappings[id].page_urls.push_back(GURL(old_icons.ColumnStringView(2)));
  }

  return icon_mappings;
}

bool FaviconDatabase::GetFaviconBitmapIDSizes(
    favicon_base::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 FaviconDatabase::GetFaviconBitmaps(
    favicon_base::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, last_requested "
      "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 = statement.ColumnTime(1);
    if (std::vector<uint8_t> bitmap_data_blob = statement.ColumnBlobAsVector(2);
        !bitmap_data_blob.empty()) {
      favicon_bitmap.bitmap_data = base::MakeRefCounted<base::RefCountedBytes>(
          std::move(bitmap_data_blob));
    }
    favicon_bitmap.pixel_size =
        gfx::Size(statement.ColumnInt(3), statement.ColumnInt(4));
    favicon_bitmap.last_requested = statement.ColumnTime(5);
    favicon_bitmaps->push_back(favicon_bitmap);
  }
  return result;
}

bool FaviconDatabase::GetFaviconBitmap(
    FaviconBitmapID bitmap_id,
    base::Time* last_updated,
    base::Time* last_requested,
    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, last_requested "
      "FROM favicon_bitmaps WHERE id=?"));
  statement.BindInt64(0, bitmap_id);

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

  if (last_updated) {
    *last_updated = statement.ColumnTime(0);
  }

  if (png_icon_data) {
    std::vector<uint8_t> png_data_blob = statement.ColumnBlobAsVector(1);
    if (!png_data_blob.empty())
      *png_icon_data =
          base::MakeRefCounted<base::RefCountedBytes>(std::move(png_data_blob));
  }

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

  if (last_requested) {
    *last_requested = statement.ColumnTime(4);
  }

  return true;
}

FaviconBitmapID FaviconDatabase::AddFaviconBitmap(
    favicon_base::FaviconID icon_id,
    scoped_refptr<base::RefCountedMemory> icon_data,
    FaviconBitmapType type,
    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, "
      "last_requested, width, height) VALUES (?, ?, ?, ?, ?, ?)"));

  statement.BindInt64(0, icon_id);
  if (icon_data.get() && icon_data->size()) {
    statement.BindBlob(1, std::move(icon_data));
  } else {
    statement.BindNull(1);
  }

  // On-visit bitmaps:
  //  - keep track of last_updated: last write time is used for expiration;
  //  - always have last_requested==0: no need to keep track of last read time.
  type == ON_VISIT ? statement.BindTime(2, time) : statement.BindInt64(2, 0);
  // On-demand bitmaps:
  //  - always have last_updated==0: last write time is not stored as they are
  //    always expired and thus ready to be replaced by ON_VISIT icons;
  //  - keep track of last_requested: last read time is used for cache eviction.
  type == ON_DEMAND ? statement.BindTime(3, time) : statement.BindInt64(3, 0);

  statement.BindInt(4, pixel_size.width());
  statement.BindInt(5, pixel_size.height());

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

bool FaviconDatabase::SetFaviconBitmap(
    FaviconBitmapID bitmap_id,
    scoped_refptr<base::RefCountedMemory> bitmap_data,
    base::Time time) {
  DCHECK(bitmap_id);
  // By updating last_updated timestamp, we assume the icon is of type ON_VISIT.
  // If it is ON_DEMAND, reset last_requested to 0 and thus silently change the
  // type to ON_VISIT.
  sql::Statement statement(
      db_.GetCachedStatement(SQL_FROM_HERE,
                             "UPDATE favicon_bitmaps SET image_data=?, "
                             "last_updated=?, last_requested=? WHERE id=?"));
  if (bitmap_data.get() && bitmap_data->size()) {
    statement.BindBlob(0, std::move(bitmap_data));
  } else {
    statement.BindNull(0);
  }
  statement.BindTime(1, time);
  statement.BindInt64(2, 0);
  statement.BindInt64(3, bitmap_id);

  return statement.Run();
}

bool FaviconDatabase::SetFaviconBitmapLastUpdateTime(FaviconBitmapID bitmap_id,
                                                     base::Time time) {
  DCHECK(bitmap_id);
  // By updating last_updated timestamp, we assume the icon is of type ON_VISIT.
  // If it is ON_DEMAND, reset last_requested to 0 and thus silently change the
  // type to ON_VISIT.
  sql::Statement statement(
      db_.GetCachedStatement(SQL_FROM_HERE,
                             "UPDATE favicon_bitmaps SET last_updated=?, "
                             "last_requested=? WHERE id=?"));
  statement.BindTime(0, time);
  statement.BindInt64(1, 0);
  statement.BindInt64(2, bitmap_id);
  return statement.Run();
}

bool FaviconDatabase::SetFaviconsOutOfDateBetween(base::Time begin,
                                                  base::Time end) {
  if (end.is_null())
    end = base::Time::Max();
  sql::Statement statement(
      db_.GetCachedStatement(SQL_FROM_HERE,
                             "UPDATE favicon_bitmaps SET last_updated=0 "
                             "WHERE last_updated>=? AND last_updated<?"));
  statement.BindTime(0, begin);
  statement.BindTime(1, end);
  return statement.Run();
}

bool FaviconDatabase::TouchOnDemandFavicon(const GURL& icon_url,
                                           base::Time time) {
  // Look up the icon ids for the url.
  sql::Statement id_statement(db_.GetCachedStatement(
      SQL_FROM_HERE, "SELECT id FROM favicons WHERE url=?"));
  id_statement.BindString(0, database_utils::GurlToDatabaseUrl(icon_url));

  base::Time max_time = time - base::Days(kFaviconUpdateLastRequestedAfterDays);

  while (id_statement.Step()) {
    favicon_base::FaviconID icon_id = id_statement.ColumnInt64(0);

    // Update the time only for ON_DEMAND bitmaps (i.e. with last_requested >
    // 0). For performance reasons, update the time only if the currently stored
    // time is old enough (UPDATEs where the WHERE condition does not match any
    // entries are way faster than UPDATEs that really change some data).
    sql::Statement statement(db_.GetCachedStatement(
        SQL_FROM_HERE,
        "UPDATE favicon_bitmaps SET last_requested=? WHERE icon_id=? AND "
        "last_requested>0 AND last_requested<=?"));
    statement.BindTime(0, time);
    statement.BindInt64(1, icon_id);
    statement.BindTime(2, max_time);
    if (!statement.Run())
      return false;
  }
  return true;
}

bool FaviconDatabase::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 FaviconDatabase::SetFaviconOutOfDate(favicon_base::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();
}

bool FaviconDatabase::GetFaviconLastUpdatedTime(favicon_base::FaviconID icon_id,
                                                base::Time* last_updated) {
  sql::Statement statement(db_.GetCachedStatement(
      SQL_FROM_HERE,
      "SELECT MAX(last_updated) FROM favicon_bitmaps WHERE icon_id=?"));
  statement.BindInt64(0, icon_id);

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

  // Return false also if there there is no bitmap with `icon_id`.
  if (statement.GetColumnType(0) == sql::ColumnType::kNull)
    return false;

  if (last_updated) {
    *last_updated = statement.ColumnTime(0);
  }
  return true;
}

favicon_base::FaviconID FaviconDatabase::GetFaviconIDForFaviconURL(
    const GURL& icon_url,
    favicon_base::IconType icon_type,
    const url::Origin& page_origin) {
  // Look to see if there even is any relevant cached entry.
  auto const icon_id = GetFaviconIDForFaviconURL(icon_url, icon_type);
  if (!icon_id) {
    return icon_id;
  }

  // Check existing mappings to see if any are for the same origin.
  sql::Statement statement(db_.GetCachedStatement(
      SQL_FROM_HERE, "SELECT page_url FROM icon_mapping WHERE icon_id=?"));
  statement.BindInt64(0, icon_id);
  while (statement.Step()) {
    const auto candidate_origin =
        url::Origin::Create(GURL(statement.ColumnStringView(0)));
    if (candidate_origin == page_origin) {
      return icon_id;
    }
  }

  // Act as if there is no entry in the cache if no mapping exists.
  return 0;
}

favicon_base::FaviconID FaviconDatabase::GetFaviconIDForFaviconURL(
    const GURL& icon_url,
    favicon_base::IconType icon_type) {
  sql::Statement statement(db_.GetCachedStatement(
      SQL_FROM_HERE, "SELECT id FROM favicons WHERE url=? AND icon_type=?"));
  statement.BindString(0, database_utils::GurlToDatabaseUrl(icon_url));
  statement.BindInt(1, ToPersistedIconType(icon_type));

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

  return statement.ColumnInt64(0);
}

bool FaviconDatabase::GetFaviconHeader(favicon_base::FaviconID icon_id,
                                       GURL* icon_url,
                                       favicon_base::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.ColumnStringView(0));
  if (icon_type)
    *icon_type = FromPersistedIconType(statement.ColumnInt(1));

  return true;
}

favicon_base::FaviconID FaviconDatabase::AddFavicon(
    const GURL& icon_url,
    favicon_base::IconType icon_type) {
  sql::Statement statement(db_.GetCachedStatement(
      SQL_FROM_HERE, "INSERT INTO favicons (url, icon_type) VALUES (?, ?)"));
  statement.BindString(0, database_utils::GurlToDatabaseUrl(icon_url));
  statement.BindInt(1, ToPersistedIconType(icon_type));

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

favicon_base::FaviconID FaviconDatabase::AddFavicon(
    const GURL& icon_url,
    favicon_base::IconType icon_type,
    scoped_refptr<base::RefCountedMemory> icon_data,
    FaviconBitmapType type,
    base::Time time,
    const gfx::Size& pixel_size) {
  favicon_base::FaviconID icon_id = AddFavicon(icon_url, icon_type);
  if (!icon_id || !AddFaviconBitmap(icon_id, std::move(icon_data), type, time,
                                    pixel_size)) {
    return 0;
  }

  return icon_id;
}

bool FaviconDatabase::DeleteFavicon(favicon_base::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 FaviconDatabase::GetIconMappingsForPageURL(
    const GURL& page_url,
    const favicon_base::IconTypeSet& 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 (auto m = mapping_data.begin(); m != mapping_data.end(); ++m) {
    if (required_icon_types.count(m->icon_type) != 0) {
      result = true;
      if (!filtered_mapping_data)
        return result;

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

bool FaviconDatabase::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, icon_mapping.page_url_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"));
  statement.BindString(0, database_utils::GurlToDatabaseUrl(page_url));

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

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

std::optional<std::pair<GURL, PageUrlType>>
FaviconDatabase::FindBestPageURLForHost(
    const GURL& url,
    const favicon_base::IconTypeSet& required_icon_types) {
  if (url.GetHost().empty()) {
    return std::nullopt;
  }

  // This query prioritizes PageUrlType::kRegular over PageUrlType::kRedirect.
  // If PageUrlType is ever changed the ORDER BY clause for page_url_type may
  // need to be revised.
  CHECK_EQ(PageUrlType::kRedirect, PageUrlType::kMaxValue);
  sql::Statement statement(
      db_.GetCachedStatement(SQL_FROM_HERE,
                             "SELECT icon_mapping.page_url, "
                             "favicons.icon_type, icon_mapping.page_url_type "
                             "FROM icon_mapping "
                             "INNER JOIN favicons "
                             "ON icon_mapping.icon_id = favicons.id "
                             "WHERE (page_url >= ? AND page_url < ?) "
                             "OR (page_url >= ? AND page_url < ?) "
                             "ORDER BY icon_mapping.page_url_type ASC, "
                             "favicons.icon_type DESC"));

  // This is an optimization to avoid using the LIKE operator which can be
  // expensive. This statement finds all rows where page_url starts from either
  // "http://<host>/" or "https://<host>/".
  std::string http_prefix =
      base::StringPrintf("http://%s/", url.GetHost().c_str());
  statement.BindString(0, http_prefix);
  statement.BindString(1, database_utils::UpperBoundString(http_prefix));
  std::string https_prefix =
      base::StringPrintf("https://%s/", url.GetHost().c_str());
  statement.BindString(2, https_prefix);
  statement.BindString(3, database_utils::UpperBoundString(https_prefix));

  while (statement.Step()) {
    favicon_base::IconType icon_type =
        FaviconDatabase::FromPersistedIconType(statement.ColumnInt(1));

    if (required_icon_types.count(icon_type) != 0) {
      return std::make_optional(std::make_pair(
          GURL(statement.ColumnStringView(0)),
          FaviconDatabase::FromPersistedPageUrlType(statement.ColumnInt64(2))));
    }
  }
  return std::nullopt;
}

IconMappingID FaviconDatabase::AddIconMapping(const GURL& page_url,
                                              favicon_base::FaviconID icon_id,
                                              PageUrlType page_url_type) {
  static const char kSql[] =
      "INSERT INTO icon_mapping (page_url, icon_id, page_url_type) "
      "VALUES (?, ?, ?)";
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSql));
  statement.BindString(0, database_utils::GurlToDatabaseUrl(page_url));
  statement.BindInt64(1, icon_id);
  statement.BindInt64(2, ToPersistedPageUrlType(page_url_type));

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

  return db_.GetLastInsertRowId();
}

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

  return statement.Run();
}

bool FaviconDatabase::DeleteIconMappingsForFaviconId(
    favicon_base::FaviconID id) {
  // This is called rarely during history expiration cleanup and hence not
  // worth caching.
  sql::Statement statement(
      db_.GetUniqueStatement("DELETE FROM icon_mapping WHERE icon_id=?"));
  statement.BindInt64(0, id);
  return statement.Run();
}

bool FaviconDatabase::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 FaviconDatabase::HasMappingFor(favicon_base::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();
}

std::vector<favicon_base::FaviconID>
FaviconDatabase::GetFaviconsLastUpdatedBefore(base::Time time, int max_count) {
  sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE,
                                                  "SELECT icon_id "
                                                  "FROM favicon_bitmaps "
                                                  "WHERE last_updated < ? "
                                                  "ORDER BY last_updated ASC "
                                                  "LIMIT ?"));
  statement.BindTime(0, time);
  statement.BindInt64(
      1, max_count == 0 ? std::numeric_limits<int64_t>::max() : max_count);
  std::vector<favicon_base::FaviconID> ids;
  while (statement.Step())
    ids.push_back(statement.ColumnInt64(0));
  return ids;
}

bool FaviconDatabase::InitIconMappingEnumerator(
    favicon_base::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, ToPersistedIconType(type));
  return enumerator->statement_.is_valid();
}

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

  // Populate temp.retained_urls with `urls_to_keep`.
  {
    static const char kCreateRetainedUrls[] =
        "CREATE TEMP TABLE retained_urls (url LONGVARCHAR PRIMARY KEY)";
    if (!db_.Execute(kCreateRetainedUrls))
      return false;

    static const char kRetainedUrlSql[] =
        "INSERT OR IGNORE INTO temp.retained_urls (url) VALUES (?)";
    sql::Statement statement(db_.GetUniqueStatement(kRetainedUrlSql));
    for (const GURL& url : urls_to_keep) {
      statement.BindString(0, database_utils::GurlToDatabaseUrl(url));
      if (!statement.Run())
        return false;
      statement.Reset(true);
    }
  }

  // temp.icon_id_mapping generates new icon ids as consecutive
  // integers starting from 1, and maps them to the old icon ids.
  {
    static 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.
    static const char kIconMappingSql[] =
        "INSERT OR IGNORE INTO temp.icon_id_mapping (old_icon_id) "
        "SELECT icon_id FROM icon_mapping "
        "JOIN temp.retained_urls "
        "ON (temp.retained_urls.url = icon_mapping.page_url)";
    if (!db_.Execute(kIconMappingSql))
      return false;
  }

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

  static const char kRenameFaviconsTable[] =
      "ALTER TABLE favicons RENAME TO old_favicons";
  static 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)";
  static const char kDropOldFaviconsTable[] = "DROP TABLE old_favicons";

  // Set the retained favicon bitmaps to be expired (last_updated == 0).
  // The user may be deleting their favicon bitmaps because the favicon bitmaps
  // are incorrect. Expiring a favicon bitmap causes it to be redownloaded when
  // the user visits a page associated with the favicon bitmap. See
  // crbug.com/474421 for an example of a bug which caused favicon bitmaps to
  // become incorrect.
  static const char kRenameFaviconBitmapsTable[] =
      "ALTER TABLE favicon_bitmaps RENAME TO old_favicon_bitmaps";
  static const char kCopyFaviconBitmaps[] =
      "INSERT INTO favicon_bitmaps "
      "  (icon_id, last_updated, image_data, width, height, last_requested) "
      "SELECT mapping.new_icon_id, 0, old.image_data, old.width, old.height,"
      "    old.last_requested "
      "FROM old_favicon_bitmaps AS old "
      "JOIN temp.icon_id_mapping AS mapping "
      "ON (old.icon_id = mapping.old_icon_id)";
  static 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;

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

  return transaction.Commit();
}

// static
int FaviconDatabase::ToPersistedIconType(favicon_base::IconType icon_type) {
  if (icon_type == favicon_base::IconType::kInvalid)
    return 0;

  return 1 << (static_cast<int>(icon_type) - 1);
}

// static
favicon_base::IconType FaviconDatabase::FromPersistedIconType(int icon_type) {
  if (icon_type == 0)
    return favicon_base::IconType::kInvalid;

  int val = std::bit_width<uint32_t>(icon_type);
  if (val > static_cast<int>(favicon_base::IconType::kMax))
    return favicon_base::IconType::kInvalid;

  return static_cast<favicon_base::IconType>(val);
}

// static
int FaviconDatabase::ToPersistedPageUrlType(PageUrlType page_url_type) {
  return static_cast<int>(page_url_type);
}

// static
PageUrlType FaviconDatabase::FromPersistedPageUrlType(int page_url_type) {
  return static_cast<PageUrlType>(page_url_type);
}

// static
void FaviconDatabase::FillIconMapping(const GURL& page_url,
                                      sql::Statement& statement,
                                      IconMapping* icon_mapping) {
  icon_mapping->mapping_id = statement.ColumnInt64(0);
  icon_mapping->icon_id = statement.ColumnInt64(1);
  icon_mapping->icon_type = FromPersistedIconType(statement.ColumnInt(2));
  icon_mapping->icon_url = GURL(statement.ColumnStringView(3));
  icon_mapping->page_url = page_url;
  icon_mapping->page_url_type =
      FromPersistedPageUrlType(statement.ColumnInt64(4));
}

sql::InitStatus FaviconDatabase::OpenDatabase(sql::Database* db,
                                              const base::FilePath& db_name) {
  // `OpenDatabase()` may be called repeatedly on the same `db`. Ensure that we
  // don't attempt to overwrite an existing error callback.
  if (!db_.has_error_callback()) {
    db->set_error_callback(base::BindRepeating(&DatabaseErrorCallback, db));
  }
  return db->Open(db_name) ? sql::INIT_OK : sql::INIT_FAILURE;
}

sql::InitStatus FaviconDatabase::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);
  if (sql::MetaTable::RazeIfIncompatible(
          &db_, /*lowest_supported_version=*/kDeprecatedVersionNumber + 1,
          kCurrentVersionNumber) == sql::RazeIfIncompatibleResult::kFailed) {
    return sql::INIT_FAILURE;
  }

  // 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 BUILDFLAG(IS_APPLE)
  // Exclude the favicons file from backups.
  base::apple::SetBackupExclusion(db_name);
#endif

  // thumbnails table has been obsolete for a long time, remove any detritus.
  std::ignore = 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.
  std::ignore = db_.Execute("DROP TABLE IF EXISTS temp_favicons");
  std::ignore = db_.Execute("DROP TABLE IF EXISTS temp_favicon_bitmaps");
  std::ignore = 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;
  }

  int cur_version = meta_table_.GetVersionNumber();

  if (cur_version == 8) {
    ++cur_version;
    if (!UpgradeToVersion9()) {
      return CantUpgradeToVersion(cur_version);
    }
  }

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

  // Initialization is complete.
  if (!transaction.Commit()) {
    LOG(ERROR) << "Favicon init transaction commit failure.";
    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.";

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

  return sql::INIT_OK;
}

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

bool FaviconDatabase::UpgradeToVersion9() {
  // Add the page_url_type column to the icon_mapping table.
  static const char kIconMappingAddPageUrlTypeSql[] =
      "ALTER TABLE icon_mapping ADD COLUMN page_url_type INTEGER DEFAULT 0";
  if (!db_.Execute(kIconMappingAddPageUrlTypeSql)) {
    return false;
  }

  return meta_table_.SetVersionNumber(9) &&
         meta_table_.SetCompatibleVersionNumber(
             std::min(9, kCompatibleVersionNumber));
}

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

}  // namespace favicon
