| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/history/thumbnail_database.h" |
| |
| #include <algorithm> |
| #include <string> |
| |
| #include "base/bind.h" |
| #include "base/debug/alias.h" |
| #include "base/file_util.h" |
| #include "base/format_macros.h" |
| #include "base/memory/ref_counted_memory.h" |
| #include "base/metrics/histogram.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/history/history_publisher.h" |
| #include "chrome/browser/history/url_database.h" |
| #include "chrome/common/chrome_version_info.h" |
| #include "chrome/common/dump_without_crashing.h" |
| #include "sql/statement.h" |
| #include "sql/transaction.h" |
| #include "third_party/sqlite/sqlite3.h" |
| |
| #if defined(OS_MACOSX) |
| #include "base/mac/mac_util.h" |
| #endif |
| |
| // Description of database tables: |
| // |
| // icon_mapping |
| // id Unique ID. |
| // page_url Page URL which has one or more associated favicons. |
| // icon_id The ID of favicon that this mapping maps to. |
| // |
| // favicons This table associates a row to each favicon for a |
| // |page_url| in the |icon_mapping| table. This is the |
| // default favicon |page_url|/favicon.ico plus any favicons |
| // associated via <link rel="icon_type" href="url">. |
| // The |id| matches the |icon_id| field in the appropriate |
| // row in the icon_mapping table. |
| // |
| // id Unique ID. |
| // url The URL at which the favicon file is located. |
| // icon_type The type of the favicon specified in the rel attribute of |
| // the link tag. The FAVICON type is used for the default |
| // favicon.ico favicon. |
| // |
| // favicon_bitmaps This table contains the PNG encoded bitmap data of the |
| // favicons. There is a separate row for every size in a |
| // multi resolution bitmap. The bitmap data is associated |
| // to the favicon via the |icon_id| field which matches |
| // the |id| field in the appropriate row in the |favicons| |
| // table. |
| // |
| // id Unique ID. |
| // icon_id The ID of the favicon that the bitmap is associated to. |
| // last_updated The time at which this favicon was inserted into the |
| // table. This is used to determine if it needs to be |
| // redownloaded from the web. |
| // image_data PNG encoded data of the favicon. |
| // width Pixel width of |image_data|. |
| // height Pixel height of |image_data|. |
| |
| namespace { |
| |
| void FillIconMapping(const sql::Statement& statement, |
| const GURL& page_url, |
| history::IconMapping* icon_mapping) { |
| icon_mapping->mapping_id = statement.ColumnInt64(0); |
| icon_mapping->icon_id = statement.ColumnInt64(1); |
| icon_mapping->icon_type = |
| static_cast<chrome::IconType>(statement.ColumnInt(2)); |
| icon_mapping->icon_url = GURL(statement.ColumnString(3)); |
| icon_mapping->page_url = page_url; |
| } |
| |
| enum InvalidStructureType { |
| // NOTE(shess): Intentionally skip bucket 0 to account for |
| // conversion from a boolean histogram. |
| STRUCTURE_EVENT_FAVICON = 1, |
| STRUCTURE_EVENT_VERSION4, |
| STRUCTURE_EVENT_VERSION5, |
| |
| // Always keep this at the end. |
| STRUCTURE_EVENT_MAX, |
| }; |
| |
| void RecordInvalidStructure(InvalidStructureType invalid_type) { |
| UMA_HISTOGRAM_ENUMERATION("History.InvalidFaviconsDBStructure", |
| invalid_type, STRUCTURE_EVENT_MAX); |
| } |
| |
| // Attempt to pass 2000 bytes of |debug_info| into a crash dump. |
| void DumpWithoutCrashing2000(const std::string& debug_info) { |
| char debug_buf[2000]; |
| base::strlcpy(debug_buf, debug_info.c_str(), arraysize(debug_buf)); |
| base::debug::Alias(&debug_buf); |
| |
| logging::DumpWithoutCrashing(); |
| } |
| |
| void ReportCorrupt(sql::Connection* db, size_t startup_kb) { |
| // Buffer for accumulating debugging info about the error. Place |
| // more-relevant information earlier, in case things overflow the |
| // fixed-size buffer. |
| std::string debug_info; |
| |
| base::StringAppendF(&debug_info, "SQLITE_CORRUPT, integrity_check:\n"); |
| |
| // Check files up to 8M to keep things from blocking too long. |
| const size_t kMaxIntegrityCheckSize = 8192; |
| if (startup_kb > kMaxIntegrityCheckSize) { |
| base::StringAppendF(&debug_info, "too big %" PRIuS "\n", startup_kb); |
| } else { |
| std::vector<std::string> messages; |
| |
| const base::TimeTicks before = base::TimeTicks::Now(); |
| db->IntegrityCheck(&messages); |
| base::StringAppendF(&debug_info, "# %" PRIx64 " ms, %" PRIuS " records\n", |
| (base::TimeTicks::Now() - before).InMilliseconds(), |
| messages.size()); |
| |
| // SQLite returns up to 100 messages by default, trim deeper to |
| // keep close to the 2000-character size limit for dumping. |
| // |
| // TODO(shess): If the first 20 tend to be actionable, test if |
| // passing the count to integrity_check makes it exit earlier. In |
| // that case it may be possible to greatly ease the size |
| // restriction. |
| const size_t kMaxMessages = 20; |
| for (size_t i = 0; i < kMaxMessages && i < messages.size(); ++i) { |
| base::StringAppendF(&debug_info, "%s\n", messages[i].c_str()); |
| } |
| } |
| |
| DumpWithoutCrashing2000(debug_info); |
| } |
| |
| void ReportError(sql::Connection* db, int error) { |
| // Buffer for accumulating debugging info about the error. Place |
| // more-relevant information earlier, in case things overflow the |
| // fixed-size buffer. |
| std::string debug_info; |
| |
| // The error message from the failed operation. |
| base::StringAppendF(&debug_info, "db error: %d/%s\n", |
| db->GetErrorCode(), db->GetErrorMessage()); |
| |
| // System errno information. |
| base::StringAppendF(&debug_info, "errno: %d\n", db->GetLastErrno()); |
| |
| // SQLITE_ERROR reports seem to be attempts to upgrade invalid |
| // schema, try to log that info. |
| if (error == SQLITE_ERROR) { |
| const char* kVersionSql = "SELECT value FROM meta WHERE key = 'version'"; |
| if (db->IsSQLValid(kVersionSql)) { |
| sql::Statement statement(db->GetUniqueStatement(kVersionSql)); |
| if (statement.Step()) { |
| debug_info += "version: "; |
| debug_info += statement.ColumnString(0); |
| debug_info += '\n'; |
| } else if (statement.Succeeded()) { |
| debug_info += "version: none\n"; |
| } else { |
| debug_info += "version: error\n"; |
| } |
| } else { |
| debug_info += "version: invalid\n"; |
| } |
| |
| debug_info += "schema:\n"; |
| |
| // sqlite_master has columns: |
| // type - "index" or "table". |
| // name - name of created element. |
| // tbl_name - name of element, or target table in case of index. |
| // rootpage - root page of the element in database file. |
| // sql - SQL to create the element. |
| // In general, the |sql| column is sufficient to derive the other |
| // columns. |rootpage| is not interesting for debugging, without |
| // the contents of the database. The COALESCE is because certain |
| // automatic elements will have a |name| but no |sql|, |
| const char* kSchemaSql = "SELECT COALESCE(sql, name) FROM sqlite_master"; |
| sql::Statement statement(db->GetUniqueStatement(kSchemaSql)); |
| while (statement.Step()) { |
| debug_info += statement.ColumnString(0); |
| debug_info += '\n'; |
| } |
| if (!statement.Succeeded()) |
| debug_info += "error\n"; |
| } |
| |
| // TODO(shess): Think of other things to log. Not logging the |
| // statement text because the backtrace should suffice in most |
| // cases. The database schema is a possibility, but the |
| // likelihood of recursive error callbacks makes that risky (same |
| // reasoning applies to other data fetched from the database). |
| |
| DumpWithoutCrashing2000(debug_info); |
| } |
| |
| // TODO(shess): If this proves out, perhaps lift the code out to |
| // chrome/browser/diagnostics/sqlite_diagnostics.{h,cc}. |
| void DatabaseErrorCallback(sql::Connection* db, |
| size_t startup_kb, |
| int 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. |
| |
| // Infrequently report information about the error up to the crash |
| // server. |
| static const uint64 kReportsPerMillion = 50000; |
| |
| // TODO(shess): For now, don't report on beta or stable so as not to |
| // overwhelm the crash server. Once the big fish are fried, |
| // consider reporting at a reduced rate on the bigger channels. |
| chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); |
| |
| // Since some/most errors will not resolve themselves, only report |
| // once per Chrome run. |
| static bool reported = false; |
| |
| if (channel != chrome::VersionInfo::CHANNEL_STABLE && |
| channel != chrome::VersionInfo::CHANNEL_BETA && |
| !reported) { |
| uint64 rand = base::RandGenerator(1000000); |
| if (error == SQLITE_CORRUPT) { |
| // Once the database is known to be corrupt, it will generate a |
| // stream of errors until someone fixes it, so give one chance. |
| // Set first in case of errors in generating the report. |
| reported = true; |
| |
| // Corrupt cases currently dominate, report them very infrequently. |
| static const uint64 kCorruptReportsPerMillion = 10000; |
| if (rand < kCorruptReportsPerMillion) |
| ReportCorrupt(db, startup_kb); |
| } else if (error == SQLITE_READONLY) { |
| // SQLITE_READONLY appears similar to SQLITE_CORRUPT - once it |
| // is seen, it is almost guaranteed to be seen again. |
| reported = true; |
| |
| if (rand < kReportsPerMillion) |
| ReportError(db, error); |
| } else { |
| // Only set the flag when making a report. This should allow |
| // later (potentially different) errors in a stream of errors to |
| // be reported. |
| // |
| // TODO(shess): Would it be worthwile to audit for which cases |
| // want once-only handling? Sqlite.Error.Thumbnail shows |
| // CORRUPT and READONLY as almost 95% of all reports on these |
| // channels, so probably easier to just harvest from the field. |
| if (rand < kReportsPerMillion) { |
| reported = true; |
| ReportError(db, error); |
| } |
| } |
| } |
| |
| // The default handling is to assert on debug and to ignore on release. |
| DLOG(FATAL) << db->GetErrorMessage(); |
| } |
| |
| } // namespace |
| |
| namespace history { |
| |
| // Version 7: 911a634d/r209424 by qsr@chromium.org on 2013-07-01 |
| // Version 6: 610f923b/r152367 by pkotwicz@chromium.org on 2012-08-20 |
| // Version 5: e2ee8ae9/r105004 by groby@chromium.org on 2011-10-12 |
| // Version 4: 5f104d76/r77288 by sky@chromium.org on 2011-03-08 |
| // Version 3: 09911bf3/r15 by initial.commit on 2008-07-26 |
| |
| // 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. |
| static const int kCurrentVersionNumber = 7; |
| static const int kCompatibleVersionNumber = 7; |
| |
| ThumbnailDatabase::IconMappingEnumerator::IconMappingEnumerator() { |
| } |
| |
| ThumbnailDatabase::IconMappingEnumerator::~IconMappingEnumerator() { |
| } |
| |
| bool ThumbnailDatabase::IconMappingEnumerator::GetNextIconMapping( |
| IconMapping* icon_mapping) { |
| if (!statement_.Step()) |
| return false; |
| FillIconMapping(statement_, GURL(statement_.ColumnString(4)), icon_mapping); |
| return true; |
| } |
| |
| ThumbnailDatabase::ThumbnailDatabase() |
| : history_publisher_(NULL) { |
| } |
| |
| sql::InitStatus ThumbnailDatabase::CantUpgradeToVersion(int cur_version) { |
| LOG(WARNING) << "Unable to update to thumbnail database to version " << |
| cur_version << "."; |
| db_.Close(); |
| return sql::INIT_FAILURE; |
| } |
| |
| ThumbnailDatabase::~ThumbnailDatabase() { |
| // The DBCloseScoper will delete the DB and the cache. |
| } |
| |
| sql::InitStatus ThumbnailDatabase::Init( |
| const base::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 from backups. |
| base::mac::SetFileBackupExclusion(db_name); |
| #endif |
| |
| // thumbnails table has been obsolete for a long time, remove any |
| // detrious. |
| ignore_result(db_.Execute("DROP TABLE IF EXISTS thumbnails")); |
| |
| // Create the tables. |
| if (!meta_table_.Init(&db_, kCurrentVersionNumber, |
| kCompatibleVersionNumber) || |
| !InitFaviconBitmapsTable(&db_) || |
| !InitFaviconBitmapsIndex() || |
| !InitFaviconsTable(&db_) || |
| !InitFaviconsIndex() || |
| !InitIconMappingTable(&db_) || |
| !InitIconMappingIndex()) { |
| db_.Close(); |
| return sql::INIT_FAILURE; |
| } |
| |
| // Version check. We should not encounter a database too old for us to handle |
| // in the wild, so we try to continue in that case. |
| if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) { |
| LOG(WARNING) << "Thumbnail database is too new."; |
| return sql::INIT_TOO_NEW; |
| } |
| |
| int cur_version = meta_table_.GetVersionNumber(); |
| if (cur_version == 2) { |
| ++cur_version; |
| if (!UpgradeToVersion3()) |
| return CantUpgradeToVersion(cur_version); |
| } |
| |
| if (cur_version == 3) { |
| ++cur_version; |
| if (!UpgradeToVersion4() || !MigrateIconMappingData(url_db)) |
| return CantUpgradeToVersion(cur_version); |
| } |
| |
| if (!db_.DoesColumnExist("favicons", "icon_type")) { |
| LOG(ERROR) << "Raze because of missing favicon.icon_type"; |
| RecordInvalidStructure(STRUCTURE_EVENT_VERSION4); |
| |
| db_.RazeAndClose(); |
| return sql::INIT_FAILURE; |
| } |
| |
| if (cur_version == 4) { |
| ++cur_version; |
| if (!UpgradeToVersion5()) |
| return CantUpgradeToVersion(cur_version); |
| } |
| |
| if (cur_version < 7 && !db_.DoesColumnExist("favicons", "sizes")) { |
| LOG(ERROR) << "Raze because of missing favicon.sizes"; |
| RecordInvalidStructure(STRUCTURE_EVENT_VERSION5); |
| |
| db_.RazeAndClose(); |
| return sql::INIT_FAILURE; |
| } |
| |
| if (cur_version == 5) { |
| ++cur_version; |
| if (!UpgradeToVersion6()) |
| return CantUpgradeToVersion(cur_version); |
| } |
| |
| if (cur_version == 6) { |
| ++cur_version; |
| if (!UpgradeToVersion7()) |
| return CantUpgradeToVersion(cur_version); |
| } |
| |
| LOG_IF(WARNING, cur_version < kCurrentVersionNumber) << |
| "Thumbnail database version " << cur_version << " is too old to handle."; |
| |
| // Initialization is complete. |
| if (!transaction.Commit()) { |
| db_.Close(); |
| return sql::INIT_FAILURE; |
| } |
| |
| // Raze the database if the structure of the favicons database is not what |
| // it should be. This error cannot be detected via the SQL error code because |
| // the error code for running SQL statements against a database with missing |
| // columns is SQLITE_ERROR which is not unique enough to act upon. |
| // TODO(pkotwicz): Revisit this in M27 and see if the razing can be removed. |
| // (crbug.com/166453) |
| if (IsFaviconDBStructureIncorrect()) { |
| LOG(ERROR) << "Raze because of invalid favicon db structure."; |
| RecordInvalidStructure(STRUCTURE_EVENT_FAVICON); |
| |
| db_.RazeAndClose(); |
| return sql::INIT_FAILURE; |
| } |
| |
| return sql::INIT_OK; |
| } |
| |
| sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db, |
| const base::FilePath& db_name) { |
| size_t startup_kb = 0; |
| int64 size_64; |
| if (file_util::GetFileSize(db_name, &size_64)) |
| startup_kb = static_cast<size_t>(size_64 / 1024); |
| |
| db->set_histogram_tag("Thumbnail"); |
| db->set_error_callback(base::Bind(&DatabaseErrorCallback, db, startup_kb)); |
| |
| // Thumbnails db now only stores favicons, so we don't need that big a page |
| // size or cache. |
| db->set_page_size(2048); |
| db->set_cache_size(32); |
| |
| // Run the database in exclusive mode. Nobody else should be accessing the |
| // database while we're running, and this will give somewhat improved perf. |
| db->set_exclusive_locking(); |
| |
| if (!db->Open(db_name)) |
| return sql::INIT_FAILURE; |
| |
| return sql::INIT_OK; |
| } |
| |
| void ThumbnailDatabase::ComputeDatabaseMetrics() { |
| sql::Statement favicon_count( |
| db_.GetCachedStatement(SQL_FROM_HERE, "SELECT COUNT(*) FROM favicons")); |
| UMA_HISTOGRAM_COUNTS_10000( |
| "History.NumFaviconsInDB", |
| favicon_count.Step() ? favicon_count.ColumnInt(0) : 0); |
| } |
| |
| bool ThumbnailDatabase::UpgradeToVersion3() { |
| // Version 3 migrated table thumbnails, which is obsolete. |
| meta_table_.SetVersionNumber(3); |
| meta_table_.SetCompatibleVersionNumber(std::min(3, kCompatibleVersionNumber)); |
| return true; |
| } |
| |
| bool ThumbnailDatabase::InitFaviconsTable(sql::Connection* db) { |
| const char kSql[] = |
| "CREATE TABLE IF NOT EXISTS favicons" |
| "(" |
| "id INTEGER PRIMARY KEY," |
| "url LONGVARCHAR NOT NULL," |
| // Set the default icon_type as FAVICON to be consistent with |
| // table upgrade in UpgradeToVersion4(). |
| "icon_type INTEGER DEFAULT 1" |
| ")"; |
| return db->Execute(kSql); |
| } |
| |
| bool ThumbnailDatabase::InitFaviconsIndex() { |
| // Add an index on the url column. |
| return |
| db_.Execute("CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)"); |
| } |
| |
| bool ThumbnailDatabase::InitFaviconBitmapsTable(sql::Connection* db) { |
| const char kSql[] = |
| "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" |
| ")"; |
| return db->Execute(kSql); |
| } |
| |
| bool ThumbnailDatabase::InitFaviconBitmapsIndex() { |
| // Add an index on the icon_id column. |
| return db_.Execute("CREATE INDEX IF NOT EXISTS favicon_bitmaps_icon_id ON " |
| "favicon_bitmaps(icon_id)"); |
| } |
| |
| bool ThumbnailDatabase::IsFaviconDBStructureIncorrect() { |
| return !db_.IsSQLValid("SELECT id, url, icon_type FROM favicons"); |
| } |
| |
| void ThumbnailDatabase::BeginTransaction() { |
| db_.BeginTransaction(); |
| } |
| |
| void ThumbnailDatabase::CommitTransaction() { |
| db_.CommitTransaction(); |
| } |
| |
| void ThumbnailDatabase::RollbackTransaction() { |
| db_.RollbackTransaction(); |
| } |
| |
| void ThumbnailDatabase::Vacuum() { |
| DCHECK(db_.transaction_nesting() == 0) << |
| "Can not have a transaction when vacuuming."; |
| ignore_result(db_.Execute("VACUUM")); |
| } |
| |
| void ThumbnailDatabase::TrimMemory(bool aggressively) { |
| db_.TrimMemory(aggressively); |
| } |
| |
| bool ThumbnailDatabase::GetFaviconBitmapIDSizes( |
| chrome::FaviconID icon_id, |
| std::vector<FaviconBitmapIDSize>* bitmap_id_sizes) { |
| DCHECK(icon_id); |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "SELECT id, width, height FROM favicon_bitmaps WHERE icon_id=?")); |
| statement.BindInt64(0, icon_id); |
| |
| bool result = false; |
| while (statement.Step()) { |
| result = true; |
| if (!bitmap_id_sizes) |
| return result; |
| |
| FaviconBitmapIDSize bitmap_id_size; |
| bitmap_id_size.bitmap_id = statement.ColumnInt64(0); |
| bitmap_id_size.pixel_size = gfx::Size(statement.ColumnInt(1), |
| statement.ColumnInt(2)); |
| bitmap_id_sizes->push_back(bitmap_id_size); |
| } |
| return result; |
| } |
| |
| bool ThumbnailDatabase::GetFaviconBitmaps( |
| chrome::FaviconID icon_id, |
| std::vector<FaviconBitmap>* favicon_bitmaps) { |
| DCHECK(icon_id); |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "SELECT id, last_updated, image_data, width, height FROM favicon_bitmaps " |
| "WHERE icon_id=?")); |
| statement.BindInt64(0, icon_id); |
| |
| bool result = false; |
| while (statement.Step()) { |
| result = true; |
| if (!favicon_bitmaps) |
| return result; |
| |
| FaviconBitmap favicon_bitmap; |
| favicon_bitmap.bitmap_id = statement.ColumnInt64(0); |
| favicon_bitmap.icon_id = icon_id; |
| favicon_bitmap.last_updated = |
| base::Time::FromInternalValue(statement.ColumnInt64(1)); |
| if (statement.ColumnByteLength(2) > 0) { |
| scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); |
| statement.ColumnBlobAsVector(2, &data->data()); |
| favicon_bitmap.bitmap_data = data; |
| } |
| favicon_bitmap.pixel_size = gfx::Size(statement.ColumnInt(3), |
| statement.ColumnInt(4)); |
| favicon_bitmaps->push_back(favicon_bitmap); |
| } |
| return result; |
| } |
| |
| bool ThumbnailDatabase::GetFaviconBitmap( |
| FaviconBitmapID bitmap_id, |
| base::Time* last_updated, |
| scoped_refptr<base::RefCountedMemory>* png_icon_data, |
| gfx::Size* pixel_size) { |
| DCHECK(bitmap_id); |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "SELECT last_updated, image_data, width, height FROM favicon_bitmaps " |
| "WHERE id=?")); |
| statement.BindInt64(0, bitmap_id); |
| |
| if (!statement.Step()) |
| return false; |
| |
| if (last_updated) |
| *last_updated = base::Time::FromInternalValue(statement.ColumnInt64(0)); |
| |
| if (png_icon_data && statement.ColumnByteLength(1) > 0) { |
| scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); |
| statement.ColumnBlobAsVector(1, &data->data()); |
| *png_icon_data = data; |
| } |
| |
| if (pixel_size) { |
| *pixel_size = gfx::Size(statement.ColumnInt(2), |
| statement.ColumnInt(3)); |
| } |
| return true; |
| } |
| |
| FaviconBitmapID ThumbnailDatabase::AddFaviconBitmap( |
| chrome::FaviconID icon_id, |
| const scoped_refptr<base::RefCountedMemory>& icon_data, |
| base::Time time, |
| const gfx::Size& pixel_size) { |
| DCHECK(icon_id); |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "INSERT INTO favicon_bitmaps (icon_id, image_data, last_updated, width, " |
| "height) VALUES (?, ?, ?, ?, ?)")); |
| statement.BindInt64(0, icon_id); |
| if (icon_data.get() && icon_data->size()) { |
| statement.BindBlob(1, icon_data->front(), |
| static_cast<int>(icon_data->size())); |
| } else { |
| statement.BindNull(1); |
| } |
| statement.BindInt64(2, time.ToInternalValue()); |
| statement.BindInt(3, pixel_size.width()); |
| statement.BindInt(4, pixel_size.height()); |
| |
| if (!statement.Run()) |
| return 0; |
| return db_.GetLastInsertRowId(); |
| } |
| |
| bool ThumbnailDatabase::SetFaviconBitmap( |
| FaviconBitmapID bitmap_id, |
| scoped_refptr<base::RefCountedMemory> bitmap_data, |
| base::Time time) { |
| DCHECK(bitmap_id); |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "UPDATE favicon_bitmaps SET image_data=?, last_updated=? WHERE id=?")); |
| if (bitmap_data.get() && bitmap_data->size()) { |
| statement.BindBlob(0, bitmap_data->front(), |
| static_cast<int>(bitmap_data->size())); |
| } else { |
| statement.BindNull(0); |
| } |
| statement.BindInt64(1, time.ToInternalValue()); |
| statement.BindInt64(2, bitmap_id); |
| |
| return statement.Run(); |
| } |
| |
| bool ThumbnailDatabase::SetFaviconBitmapLastUpdateTime( |
| FaviconBitmapID bitmap_id, |
| base::Time time) { |
| DCHECK(bitmap_id); |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "UPDATE favicon_bitmaps SET last_updated=? WHERE id=?")); |
| statement.BindInt64(0, time.ToInternalValue()); |
| statement.BindInt64(1, bitmap_id); |
| return statement.Run(); |
| } |
| |
| bool ThumbnailDatabase::DeleteFaviconBitmap(FaviconBitmapID bitmap_id) { |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "DELETE FROM favicon_bitmaps WHERE id=?")); |
| statement.BindInt64(0, bitmap_id); |
| return statement.Run(); |
| } |
| |
| bool ThumbnailDatabase::SetFaviconOutOfDate(chrome::FaviconID icon_id) { |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "UPDATE favicon_bitmaps SET last_updated=? WHERE icon_id=?")); |
| statement.BindInt64(0, 0); |
| statement.BindInt64(1, icon_id); |
| |
| return statement.Run(); |
| } |
| |
| chrome::FaviconID ThumbnailDatabase::GetFaviconIDForFaviconURL( |
| const GURL& icon_url, |
| int required_icon_type, |
| chrome::IconType* icon_type) { |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "SELECT id, icon_type FROM favicons WHERE url=? AND (icon_type & ? > 0) " |
| "ORDER BY icon_type DESC")); |
| statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url)); |
| statement.BindInt(1, required_icon_type); |
| |
| if (!statement.Step()) |
| return 0; // not cached |
| |
| if (icon_type) |
| *icon_type = static_cast<chrome::IconType>(statement.ColumnInt(1)); |
| return statement.ColumnInt64(0); |
| } |
| |
| bool ThumbnailDatabase::GetFaviconHeader(chrome::FaviconID icon_id, |
| GURL* icon_url, |
| chrome::IconType* icon_type) { |
| DCHECK(icon_id); |
| |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "SELECT url, icon_type FROM favicons WHERE id=?")); |
| statement.BindInt64(0, icon_id); |
| |
| if (!statement.Step()) |
| return false; // No entry for the id. |
| |
| if (icon_url) |
| *icon_url = GURL(statement.ColumnString(0)); |
| if (icon_type) |
| *icon_type = static_cast<chrome::IconType>(statement.ColumnInt(1)); |
| |
| return true; |
| } |
| |
| chrome::FaviconID ThumbnailDatabase::AddFavicon( |
| const GURL& icon_url, |
| chrome::IconType icon_type) { |
| |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "INSERT INTO favicons (url, icon_type) VALUES (?, ?)")); |
| statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url)); |
| statement.BindInt(1, icon_type); |
| |
| if (!statement.Run()) |
| return 0; |
| return db_.GetLastInsertRowId(); |
| } |
| |
| chrome::FaviconID ThumbnailDatabase::AddFavicon( |
| const GURL& icon_url, |
| chrome::IconType icon_type, |
| const scoped_refptr<base::RefCountedMemory>& icon_data, |
| base::Time time, |
| const gfx::Size& pixel_size) { |
| chrome::FaviconID icon_id = AddFavicon(icon_url, icon_type); |
| if (!icon_id || !AddFaviconBitmap(icon_id, icon_data, time, pixel_size)) |
| return 0; |
| |
| return icon_id; |
| } |
| |
| bool ThumbnailDatabase::DeleteFavicon(chrome::FaviconID id) { |
| sql::Statement statement; |
| statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, |
| "DELETE FROM favicons WHERE id = ?")); |
| statement.BindInt64(0, id); |
| if (!statement.Run()) |
| return false; |
| |
| statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, |
| "DELETE FROM favicon_bitmaps WHERE icon_id = ?")); |
| statement.BindInt64(0, id); |
| return statement.Run(); |
| } |
| |
| bool ThumbnailDatabase::GetIconMappingsForPageURL( |
| const GURL& page_url, |
| int required_icon_types, |
| std::vector<IconMapping>* filtered_mapping_data) { |
| std::vector<IconMapping> mapping_data; |
| if (!GetIconMappingsForPageURL(page_url, &mapping_data)) |
| return false; |
| |
| bool result = false; |
| for (std::vector<IconMapping>::iterator m = mapping_data.begin(); |
| m != mapping_data.end(); ++m) { |
| if (m->icon_type & required_icon_types) { |
| result = true; |
| if (!filtered_mapping_data) |
| return result; |
| |
| // Restrict icon type of subsequent matches to |m->icon_type|. |
| // |m->icon_type| is the largest IconType in |mapping_data| because |
| // |mapping_data| is sorted in descending order of IconType. |
| required_icon_types = m->icon_type; |
| |
| filtered_mapping_data->push_back(*m); |
| } |
| } |
| return result; |
| } |
| |
| bool ThumbnailDatabase::GetIconMappingsForPageURL( |
| const GURL& page_url, |
| std::vector<IconMapping>* mapping_data) { |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, " |
| "favicons.url " |
| "FROM icon_mapping " |
| "INNER JOIN favicons " |
| "ON icon_mapping.icon_id = favicons.id " |
| "WHERE icon_mapping.page_url=? " |
| "ORDER BY favicons.icon_type DESC")); |
| statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); |
| |
| bool result = false; |
| while (statement.Step()) { |
| result = true; |
| if (!mapping_data) |
| return result; |
| |
| IconMapping icon_mapping; |
| FillIconMapping(statement, page_url, &icon_mapping); |
| mapping_data->push_back(icon_mapping); |
| } |
| return result; |
| } |
| |
| bool ThumbnailDatabase::UpdateIconMapping(IconMappingID mapping_id, |
| chrome::FaviconID icon_id) { |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "UPDATE icon_mapping SET icon_id=? WHERE id=?")); |
| statement.BindInt64(0, icon_id); |
| statement.BindInt64(1, mapping_id); |
| |
| return statement.Run(); |
| } |
| |
| bool ThumbnailDatabase::DeleteIconMappings(const GURL& page_url) { |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "DELETE FROM icon_mapping WHERE page_url = ?")); |
| statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); |
| |
| return statement.Run(); |
| } |
| |
| bool ThumbnailDatabase::DeleteIconMapping(IconMappingID mapping_id) { |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "DELETE FROM icon_mapping WHERE id=?")); |
| statement.BindInt64(0, mapping_id); |
| |
| return statement.Run(); |
| } |
| |
| bool ThumbnailDatabase::HasMappingFor(chrome::FaviconID id) { |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "SELECT id FROM icon_mapping " |
| "WHERE icon_id=?")); |
| statement.BindInt64(0, id); |
| |
| return statement.Step(); |
| } |
| |
| bool ThumbnailDatabase::CloneIconMappings(const GURL& old_page_url, |
| const GURL& new_page_url) { |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, |
| "SELECT icon_id FROM icon_mapping " |
| "WHERE page_url=?")); |
| if (!statement.is_valid()) |
| return false; |
| |
| // Do nothing if there are existing bindings |
| statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url)); |
| if (statement.Step()) |
| return true; |
| |
| statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, |
| "INSERT INTO icon_mapping (page_url, icon_id) " |
| "SELECT ?, icon_id FROM icon_mapping " |
| "WHERE page_url = ?")); |
| |
| statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url)); |
| statement.BindString(1, URLDatabase::GURLToDatabaseURL(old_page_url)); |
| return statement.Run(); |
| } |
| |
| bool ThumbnailDatabase::InitIconMappingEnumerator( |
| chrome::IconType type, |
| IconMappingEnumerator* enumerator) { |
| DCHECK(!enumerator->statement_.is_valid()); |
| enumerator->statement_.Assign(db_.GetCachedStatement( |
| SQL_FROM_HERE, |
| "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, " |
| "favicons.url, icon_mapping.page_url " |
| "FROM icon_mapping JOIN favicons ON (" |
| "icon_mapping.icon_id = favicons.id) " |
| "WHERE favicons.icon_type = ?")); |
| enumerator->statement_.BindInt(0, type); |
| return enumerator->statement_.is_valid(); |
| } |
| |
| bool ThumbnailDatabase::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; |
| } |
| |
| bool ThumbnailDatabase::RetainDataForPageUrls( |
| const std::vector<GURL>& urls_to_keep) { |
| sql::Transaction transaction(&db_); |
| if (!transaction.Begin()) |
| return false; |
| |
| // temp.icon_id_mapping generates new icon ids as consecutive |
| // integers starting from 1, and maps them to the old icon ids. |
| { |
| const char kIconMappingCreate[] = |
| "CREATE TEMP TABLE icon_id_mapping " |
| "(" |
| "new_icon_id INTEGER PRIMARY KEY," |
| "old_icon_id INTEGER NOT NULL UNIQUE" |
| ")"; |
| if (!db_.Execute(kIconMappingCreate)) |
| return false; |
| |
| // Insert the icon ids for retained urls, skipping duplicates. |
| const char kIconMappingSql[] = |
| "INSERT OR IGNORE INTO temp.icon_id_mapping (old_icon_id) " |
| "SELECT icon_id FROM icon_mapping WHERE page_url = ?"; |
| sql::Statement statement(db_.GetUniqueStatement(kIconMappingSql)); |
| for (std::vector<GURL>::const_iterator |
| i = urls_to_keep.begin(); i != urls_to_keep.end(); ++i) { |
| statement.BindString(0, URLDatabase::GURLToDatabaseURL(*i)); |
| if (!statement.Run()) |
| return false; |
| } |
| } |
| |
| { |
| const char kRenameIconMappingTable[] = |
| "ALTER TABLE icon_mapping RENAME TO old_icon_mapping"; |
| const char kCopyIconMapping[] = |
| "INSERT INTO icon_mapping (page_url, icon_id) " |
| "SELECT old.page_url, mapping.new_icon_id " |
| "FROM old_icon_mapping AS old " |
| "JOIN temp.icon_id_mapping AS mapping " |
| "ON (old.icon_id = mapping.old_icon_id)"; |
| const char kDropOldIconMappingTable[] = "DROP TABLE old_icon_mapping"; |
| if (!db_.Execute(kRenameIconMappingTable) || |
| !InitIconMappingTable(&db_) || |
| !db_.Execute(kCopyIconMapping) || |
| !db_.Execute(kDropOldIconMappingTable)) { |
| return false; |
| } |
| } |
| |
| { |
| const char kRenameFaviconsTable[] = |
| "ALTER TABLE favicons RENAME TO old_favicons"; |
| const char kCopyFavicons[] = |
| "INSERT INTO favicons (id, url, icon_type) " |
| "SELECT mapping.new_icon_id, old.url, old.icon_type " |
| "FROM old_favicons AS old " |
| "JOIN temp.icon_id_mapping AS mapping " |
| "ON (old.id = mapping.old_icon_id)"; |
| const char kDropOldFaviconsTable[] = "DROP TABLE old_favicons"; |
| if (!db_.Execute(kRenameFaviconsTable) || |
| !InitFaviconsTable(&db_) || |
| !db_.Execute(kCopyFavicons) || |
| !db_.Execute(kDropOldFaviconsTable)) { |
| return false; |
| } |
| } |
| |
| { |
| const char kRenameFaviconBitmapsTable[] = |
| "ALTER TABLE favicon_bitmaps RENAME TO old_favicon_bitmaps"; |
| const char kCopyFaviconBitmaps[] = |
| "INSERT INTO favicon_bitmaps " |
| " (icon_id, last_updated, image_data, width, height) " |
| "SELECT mapping.new_icon_id, old.last_updated, " |
| " old.image_data, old.width, old.height " |
| "FROM old_favicon_bitmaps AS old " |
| "JOIN temp.icon_id_mapping AS mapping " |
| "ON (old.icon_id = mapping.old_icon_id)"; |
| const char kDropOldFaviconBitmapsTable[] = |
| "DROP TABLE old_favicon_bitmaps"; |
| if (!db_.Execute(kRenameFaviconBitmapsTable) || |
| !InitFaviconBitmapsTable(&db_) || |
| !db_.Execute(kCopyFaviconBitmaps) || |
| !db_.Execute(kDropOldFaviconBitmapsTable)) { |
| return false; |
| } |
| } |
| |
| // Renaming the tables adjusts the indices to reference the new |
| // name, BUT DOES NOT RENAME THE INDICES. The DROP will drop the |
| // indices, now re-create them against the new tables. |
| if (!InitIconMappingIndex() || |
| !InitFaviconsIndex() || |
| !InitFaviconBitmapsIndex()) { |
| return false; |
| } |
| |
| const char kIconMappingDrop[] = "DROP TABLE temp.icon_id_mapping"; |
| if (!db_.Execute(kIconMappingDrop)) |
| return false; |
| |
| return transaction.Commit(); |
| } |
| |
| bool ThumbnailDatabase::InitIconMappingTable(sql::Connection* db) { |
| const char kSql[] = |
| "CREATE TABLE IF NOT EXISTS icon_mapping" |
| "(" |
| "id INTEGER PRIMARY KEY," |
| "page_url LONGVARCHAR NOT NULL," |
| "icon_id INTEGER" |
| ")"; |
| return db->Execute(kSql); |
| } |
| |
| bool ThumbnailDatabase::InitIconMappingIndex() { |
| // Add an index on the url column. |
| return |
| db_.Execute("CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx" |
| " ON icon_mapping(page_url)") && |
| db_.Execute("CREATE INDEX IF NOT EXISTS icon_mapping_icon_id_idx" |
| " ON icon_mapping(icon_id)"); |
| } |
| |
| IconMappingID ThumbnailDatabase::AddIconMapping(const GURL& page_url, |
| chrome::FaviconID icon_id) { |
| const char kSql[] = |
| "INSERT INTO icon_mapping (page_url, icon_id) VALUES (?, ?)"; |
| sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSql)); |
| statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); |
| statement.BindInt64(1, icon_id); |
| |
| if (!statement.Run()) |
| return 0; |
| |
| return db_.GetLastInsertRowId(); |
| } |
| |
| bool ThumbnailDatabase::IsLatestVersion() { |
| return meta_table_.GetVersionNumber() == kCurrentVersionNumber; |
| } |
| |
| 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")) { |
| return false; |
| } |
| meta_table_.SetVersionNumber(4); |
| meta_table_.SetCompatibleVersionNumber(std::min(4, kCompatibleVersionNumber)); |
| return true; |
| } |
| |
| bool ThumbnailDatabase::UpgradeToVersion5() { |
| if (!db_.Execute("ALTER TABLE favicons ADD sizes LONGVARCHAR")) { |
| return false; |
| } |
| meta_table_.SetVersionNumber(5); |
| meta_table_.SetCompatibleVersionNumber(std::min(5, kCompatibleVersionNumber)); |
| return true; |
| } |
| |
| bool ThumbnailDatabase::UpgradeToVersion6() { |
| bool success = |
| db_.Execute("INSERT INTO favicon_bitmaps (icon_id, last_updated, " |
| "image_data, width, height)" |
| "SELECT id, last_updated, image_data, 0, 0 FROM favicons") && |
| db_.Execute("CREATE TABLE temp_favicons (" |
| "id INTEGER PRIMARY KEY," |
| "url LONGVARCHAR NOT NULL," |
| "icon_type INTEGER DEFAULT 1," |
| // Set the default icon_type as FAVICON to be consistent with |
| // table upgrade in UpgradeToVersion4(). |
| "sizes LONGVARCHAR)") && |
| db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) " |
| "SELECT id, url, icon_type FROM favicons") && |
| db_.Execute("DROP TABLE favicons") && |
| db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons"); |
| if (!success) |
| return false; |
| |
| meta_table_.SetVersionNumber(6); |
| meta_table_.SetCompatibleVersionNumber(std::min(6, kCompatibleVersionNumber)); |
| return true; |
| } |
| |
| bool ThumbnailDatabase::UpgradeToVersion7() { |
| bool success = |
| db_.Execute("CREATE TABLE temp_favicons (" |
| "id INTEGER PRIMARY KEY," |
| "url LONGVARCHAR NOT NULL," |
| "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; |
| } |
| |
| } // namespace history |