blob: 6942f37b8c8a842577310752a50fddb4f5487504 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/favicon/core/favicon_database.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/bits.h"
#include "base/debug/alias.h"
#include "base/files/file_util.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/url_converter.h"
#include "sql/recovery.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "third_party/sqlite/sqlite3.h"
#if defined(OS_MAC)
#include "base/mac/mac_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.
//
// 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).
// Version 8: 982ef2c1/r323176 by rogerm@chromium.org on 2015-03-31
// Version 7: 911a634d/r209424 by qsr@chromium.org on 2013-07-01
// 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 = 8;
const int kCompatibleVersionNumber = 8;
const int kDeprecatedVersionNumber = 6; // and earlier.
void 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 =
FaviconDatabase::FromPersistedIconType(statement.ColumnInt(2));
icon_mapping->icon_url = GURL(statement.ColumnString(3));
icon_mapping->page_url = page_url;
}
// 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"
")";
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,
const base::FilePath& db_path,
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 corrupt databases.
if (sql::Recovery::ShouldRecover(extended_error)) {
// NOTE(shess): This approach is valid as of version 8. When bumping the
// version, it will PROBABLY remain valid, but consider whether any schema
// changes might break automated recovery.
DCHECK_EQ(8, kCurrentVersionNumber);
// Prevent reentrant calls.
db->reset_error_callback();
// 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.
// After this call, the |db| handle is poisoned so that future calls will
// return errors until the handle is re-opened.
sql::Recovery::RecoverDatabaseWithMetaVersion(db, db_path);
// The DLOG(FATAL) below is intended to draw immediate attention to errors
// in newly-written code. Database corruption is generally a result of OS
// or hardware issues, not coding errors at the client level, so displaying
// the error would probably lead to confusion. The ignored call signals the
// test-expectation framework that the error was handled.
ignore_result(sql::Database::IsExpectedSqliteError(extended_error));
return;
}
// The default handling is to assert on debug and to ignore on release.
if (!sql::Database::IsExpectedSqliteError(extended_error))
DLOG(FATAL) << db->GetErrorMessage();
}
} // namespace
FaviconDatabase::IconMappingEnumerator::IconMappingEnumerator() {}
FaviconDatabase::IconMappingEnumerator::~IconMappingEnumerator() {}
bool FaviconDatabase::IconMappingEnumerator::GetNextIconMapping(
IconMapping* icon_mapping) {
if (!statement_.Step())
return false;
FillIconMapping(GURL(statement_.ColumnString(4)), statement_, icon_mapping);
return true;
}
FaviconDatabase::FaviconDatabase()
: db_({// Run the database in exclusive mode. Nobody else should be
// accessing the database while we're running, and this will give
// somewhat improved perf.
.exclusive_locking = true,
// Favicons db only stores favicons, so we don't need that big a page
// size or cache.
.page_size = 2048,
.cache_size = 32}) {}
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 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 FaviconDatabase::ComputeDatabaseMetrics() {
base::TimeTicks start_time = base::TimeTicks::Now();
// 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);
}
// Count all icon URLs referenced by the DB.
{
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);
}
// Count all bitmap resources cached in the DB.
{
sql::Statement bitmap_count(db_.GetCachedStatement(
SQL_FROM_HERE, "SELECT COUNT(*) FROM favicon_bitmaps"));
UMA_HISTOGRAM_COUNTS_10000(
"History.NumFaviconBitmapsInDB",
bitmap_count.Step() ? bitmap_count.ColumnInt(0) : 0);
}
// Count "touch" icon URLs referenced by the DB.
{
sql::Statement touch_icon_count(db_.GetCachedStatement(
SQL_FROM_HERE,
"SELECT COUNT(*) FROM favicons WHERE icon_type IN (?, ?)"));
touch_icon_count.BindInt64(
0, ToPersistedIconType(favicon_base::IconType::kTouchIcon));
touch_icon_count.BindInt64(
1, ToPersistedIconType(favicon_base::IconType::kTouchPrecomposedIcon));
UMA_HISTOGRAM_COUNTS_10000(
"History.NumTouchIconsInDB",
touch_icon_count.Step() ? touch_icon_count.ColumnInt(0) : 0);
}
// Count "large" bitmap resources cached in the DB.
{
sql::Statement large_bitmap_count(db_.GetCachedStatement(
SQL_FROM_HERE,
"SELECT COUNT(*) FROM favicon_bitmaps WHERE width >= 64"));
UMA_HISTOGRAM_COUNTS_10000(
"History.NumLargeFaviconBitmapsInDB",
large_bitmap_count.Step() ? large_bitmap_count.ColumnInt(0) : 0);
}
// Count all icon mappings maintained by the DB.
{
sql::Statement mapping_count(db_.GetCachedStatement(
SQL_FROM_HERE, "SELECT COUNT(*) FROM icon_mapping"));
UMA_HISTOGRAM_CUSTOM_COUNTS(
"History.NumFaviconMappingsInDB",
(mapping_count.Step() ? mapping_count.ColumnInt(0) : 0), 1, 100000,
100);
}
UMA_HISTOGRAM_TIMES("History.FaviconDatabaseAdvancedMetricsTime",
base::TimeTicks::Now() - start_time);
}
void FaviconDatabase::BeginTransaction() {
db_.BeginTransaction();
}
void FaviconDatabase::CommitTransaction() {
db_.CommitTransaction();
}
void FaviconDatabase::RollbackTransaction() {
db_.RollbackTransaction();
}
void FaviconDatabase::Vacuum() {
DCHECK(db_.transaction_nesting() == 0)
<< "Can not have a transaction when vacuuming.";
ignore_result(db_.Execute("VACUUM"));
}
void FaviconDatabase::TrimMemory() {
db_.TrimMemory();
}
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.BindInt64(0, threshold.ToDeltaSinceWindowsEpoch().InMicroseconds());
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.ColumnString(1));
icon_mappings[id].page_urls.push_back(GURL(old_icons.ColumnString(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 = base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(statement.ColumnInt64(1)));
std::vector<uint8_t> bitmap_data_blob;
statement.ColumnBlobAsVector(2, &bitmap_data_blob);
if (!bitmap_data_blob.empty()) {
favicon_bitmap.bitmap_data =
base::RefCountedBytes::TakeVector(&bitmap_data_blob);
}
favicon_bitmap.pixel_size =
gfx::Size(statement.ColumnInt(3), statement.ColumnInt(4));
favicon_bitmap.last_requested = base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(statement.ColumnInt64(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 = base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(statement.ColumnInt64(0)));
}
if (png_icon_data) {
std::vector<uint8_t> png_data_blob;
statement.ColumnBlobAsVector(1, &png_data_blob);
if (!png_data_blob.empty())
*png_icon_data = base::RefCountedBytes::TakeVector(&png_data_blob);
}
if (pixel_size) {
*pixel_size = gfx::Size(statement.ColumnInt(2), statement.ColumnInt(3));
}
if (last_requested) {
*last_requested = base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(statement.ColumnInt64(4)));
}
return true;
}
FaviconBitmapID FaviconDatabase::AddFaviconBitmap(
favicon_base::FaviconID icon_id,
const 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, *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.
statement.BindInt64(2, type == ON_VISIT
? time.ToDeltaSinceWindowsEpoch().InMicroseconds()
: 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.
statement.BindInt64(3, type == ON_DEMAND
? time.ToDeltaSinceWindowsEpoch().InMicroseconds()
: 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, *bitmap_data);
} else {
statement.BindNull(0);
}
statement.BindInt64(1, time.ToDeltaSinceWindowsEpoch().InMicroseconds());
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.BindInt64(0, time.ToDeltaSinceWindowsEpoch().InMicroseconds());
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.BindInt64(0, begin.ToDeltaSinceWindowsEpoch().InMicroseconds());
statement.BindInt64(1, end.ToDeltaSinceWindowsEpoch().InMicroseconds());
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.BindInt64(0, time.ToDeltaSinceWindowsEpoch().InMicroseconds());
statement.BindInt64(1, icon_id);
statement.BindInt64(2,
max_time.ToDeltaSinceWindowsEpoch().InMicroseconds());
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 = base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(statement.ColumnInt64(0)));
}
return true;
}
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.ColumnString(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,
const 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, 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 "
"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;
}
absl::optional<GURL> FaviconDatabase::FindFirstPageURLForHost(
const GURL& url,
const favicon_base::IconTypeSet& required_icon_types) {
if (url.host().empty())
return absl::nullopt;
sql::Statement statement(
db_.GetCachedStatement(SQL_FROM_HERE,
"SELECT icon_mapping.page_url, favicons.icon_type "
"FROM icon_mapping "
"INNER JOIN favicons "
"ON icon_mapping.icon_id = favicons.id "
"WHERE icon_mapping.page_url LIKE ? "
"ORDER BY favicons.icon_type DESC"));
// Bind the host with a prefix of "://" and suffix of "/" to ensure the entire
// host name is matched.
statement.BindString(0, base::StringPrintf("%%://%s/%%", url.host().c_str()));
while (statement.Step()) {
favicon_base::IconType icon_type =
FaviconDatabase::FromPersistedIconType(statement.ColumnInt(1));
if (required_icon_types.count(icon_type) != 0)
return absl::make_optional(GURL(statement.ColumnString(0)));
}
return absl::nullopt;
}
IconMappingID FaviconDatabase::AddIconMapping(const GURL& page_url,
favicon_base::FaviconID icon_id) {
static const char kSql[] =
"INSERT INTO icon_mapping (page_url, icon_id) VALUES (?, ?)";
sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSql));
statement.BindString(0, database_utils::GurlToDatabaseUrl(page_url));
statement.BindInt64(1, icon_id);
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.BindInt64(0, time.ToDeltaSinceWindowsEpoch().InMicroseconds());
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) "
"SELECT temp.retained_urls.url, mapping.new_icon_id "
"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 = 1 + base::bits::Log2Floor(icon_type);
if (val > static_cast<int>(favicon_base::IconType::kMax))
return favicon_base::IconType::kInvalid;
return static_cast<favicon_base::IconType>(val);
}
sql::InitStatus FaviconDatabase::OpenDatabase(sql::Database* db,
const base::FilePath& db_name) {
db->set_histogram_tag("Thumbnail");
db->set_error_callback(
base::BindRepeating(&DatabaseErrorCallback, db, db_name));
if (!db->Open(db_name))
return sql::INIT_FAILURE;
db->Preload();
return sql::INIT_OK;
}
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);
sql::MetaTable::RazeIfIncompatible(
&db_, /*lowest_supported_version=*/kDeprecatedVersionNumber + 1,
kCurrentVersionNumber);
// 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_MAC)
// Exclude the favicons file from backups.
base::mac::SetFileBackupExclusion(db_name);
#endif
// thumbnails table has been obsolete for a long time, remove any detritus.
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) << "Favicon 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";
db_.RazeAndClose();
return sql::INIT_FAILURE;
}
if (cur_version < 7 && !db_.DoesColumnExist("favicons", "sizes")) {
LOG(ERROR) << "Raze because of missing favicon.sizes";
db_.RazeAndClose();
return sql::INIT_FAILURE;
}
if (cur_version == 6) {
++cur_version;
if (!UpgradeToVersion7())
return CantUpgradeToVersion(cur_version);
}
if (cur_version == 7) {
++cur_version;
if (!UpgradeToVersion8())
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())
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_.RazeAndClose();
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::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 kFavicon 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 FaviconDatabase::UpgradeToVersion8() {
// Add the last_requested column to the favicon_bitmaps table.
static const char kFaviconBitmapsAddLastRequestedSql[] =
"ALTER TABLE favicon_bitmaps ADD COLUMN last_requested INTEGER DEFAULT 0";
if (!db_.Execute(kFaviconBitmapsAddLastRequestedSql))
return false;
meta_table_.SetVersionNumber(8);
meta_table_.SetCompatibleVersionNumber(std::min(8, kCompatibleVersionNumber));
return true;
}
bool FaviconDatabase::IsFaviconDBStructureIncorrect() {
return !db_.IsSQLValid("SELECT id, url, icon_type FROM favicons");
}
} // namespace favicon