// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/history/thumbnail_database.h" #include <algorithm> #include <string> #include "base/bind.h" #include "base/debug/alias.h" #include "base/file_util.h" #include "base/format_macros.h" #include "base/memory/ref_counted_memory.h" #include "base/metrics/histogram.h" #include "base/rand_util.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/time/time.h" #include "chrome/browser/history/url_database.h" #include "chrome/common/chrome_version_info.h" #include "chrome/common/dump_without_crashing.h" #include "sql/recovery.h" #include "sql/statement.h" #include "sql/transaction.h" #include "third_party/sqlite/sqlite3.h" #if defined(OS_MACOSX) #include "base/mac/mac_util.h" #endif // Description of database tables: // // icon_mapping // id Unique ID. // page_url Page URL which has one or more associated favicons. // icon_id The ID of favicon that this mapping maps to. // // favicons This table associates a row to each favicon for a // |page_url| in the |icon_mapping| table. This is the // default favicon |page_url|/favicon.ico plus any favicons // associated via <link rel="icon_type" href="url">. // The |id| matches the |icon_id| field in the appropriate // row in the icon_mapping table. // // id Unique ID. // url The URL at which the favicon file is located. // icon_type The type of the favicon specified in the rel attribute of // the link tag. The FAVICON type is used for the default // favicon.ico favicon. // // favicon_bitmaps This table contains the PNG encoded bitmap data of the // favicons. There is a separate row for every size in a // multi resolution bitmap. The bitmap data is associated // to the favicon via the |icon_id| field which matches // the |id| field in the appropriate row in the |favicons| // table. // // id Unique ID. // icon_id The ID of the favicon that the bitmap is associated to. // last_updated The time at which this favicon was inserted into the // table. This is used to determine if it needs to be // redownloaded from the web. // image_data PNG encoded data of the favicon. // width Pixel width of |image_data|. // height Pixel height of |image_data|. namespace { // For this database, schema migrations are deprecated after two // years. This means that the oldest non-deprecated version should be // two years old or greater (thus the migrations to get there are // older). Databases containing deprecated versions will be cleared // at startup. Since this database is a cache, losing old data is not // fatal (in fact, very old data may be expired immediately at startup // anyhow). // Version 7: 911a634d/r209424 by qsr@chromium.org on 2013-07-01 // Version 6: 610f923b/r152367 by pkotwicz@chromium.org on 2012-08-20 // Version 5: e2ee8ae9/r105004 by groby@chromium.org on 2011-10-12 // Version 4: 5f104d76/r77288 by sky@chromium.org on 2011-03-08 (deprecated) // Version 3: 09911bf3/r15 by initial.commit on 2008-07-26 (deprecated) // Version number of the database. // NOTE(shess): When changing the version, add a new golden file for // the new version and a test to verify that Init() works with it. const int kCurrentVersionNumber = 7; const int kCompatibleVersionNumber = 7; const int kDeprecatedVersionNumber = 4; // and earlier. void FillIconMapping(const sql::Statement& statement, const GURL& page_url, history::IconMapping* icon_mapping) { icon_mapping->mapping_id = statement.ColumnInt64(0); icon_mapping->icon_id = statement.ColumnInt64(1); icon_mapping->icon_type = static_cast<chrome::IconType>(statement.ColumnInt(2)); icon_mapping->icon_url = GURL(statement.ColumnString(3)); icon_mapping->page_url = page_url; } enum InvalidStructureType { // NOTE(shess): Intentionally skip bucket 0 to account for // conversion from a boolean histogram. STRUCTURE_EVENT_FAVICON = 1, STRUCTURE_EVENT_VERSION4, STRUCTURE_EVENT_VERSION5, // Always keep this at the end. STRUCTURE_EVENT_MAX, }; void RecordInvalidStructure(InvalidStructureType invalid_type) { UMA_HISTOGRAM_ENUMERATION("History.InvalidFaviconsDBStructure", invalid_type, STRUCTURE_EVENT_MAX); } // Attempt to pass 2000 bytes of |debug_info| into a crash dump. void DumpWithoutCrashing2000(const std::string& debug_info) { char debug_buf[2000]; base::strlcpy(debug_buf, debug_info.c_str(), arraysize(debug_buf)); base::debug::Alias(&debug_buf); logging::DumpWithoutCrashing(); } void ReportCorrupt(sql::Connection* db, size_t startup_kb) { // Buffer for accumulating debugging info about the error. Place // more-relevant information earlier, in case things overflow the // fixed-size buffer. std::string debug_info; base::StringAppendF(&debug_info, "SQLITE_CORRUPT, integrity_check:\n"); // Check files up to 8M to keep things from blocking too long. const size_t kMaxIntegrityCheckSize = 8192; if (startup_kb > kMaxIntegrityCheckSize) { base::StringAppendF(&debug_info, "too big %" PRIuS "\n", startup_kb); } else { std::vector<std::string> messages; const base::TimeTicks before = base::TimeTicks::Now(); db->FullIntegrityCheck(&messages); base::StringAppendF(&debug_info, "# %" PRIx64 " ms, %" PRIuS " records\n", (base::TimeTicks::Now() - before).InMilliseconds(), messages.size()); // SQLite returns up to 100 messages by default, trim deeper to // keep close to the 2000-character size limit for dumping. // // TODO(shess): If the first 20 tend to be actionable, test if // passing the count to integrity_check makes it exit earlier. In // that case it may be possible to greatly ease the size // restriction. const size_t kMaxMessages = 20; for (size_t i = 0; i < kMaxMessages && i < messages.size(); ++i) { base::StringAppendF(&debug_info, "%s\n", messages[i].c_str()); } } DumpWithoutCrashing2000(debug_info); } void ReportError(sql::Connection* db, int error) { // Buffer for accumulating debugging info about the error. Place // more-relevant information earlier, in case things overflow the // fixed-size buffer. std::string debug_info; // The error message from the failed operation. base::StringAppendF(&debug_info, "db error: %d/%s\n", db->GetErrorCode(), db->GetErrorMessage()); // System errno information. base::StringAppendF(&debug_info, "errno: %d\n", db->GetLastErrno()); // SQLITE_ERROR reports seem to be attempts to upgrade invalid // schema, try to log that info. if (error == SQLITE_ERROR) { const char* kVersionSql = "SELECT value FROM meta WHERE key = 'version'"; if (db->IsSQLValid(kVersionSql)) { sql::Statement statement(db->GetUniqueStatement(kVersionSql)); if (statement.Step()) { debug_info += "version: "; debug_info += statement.ColumnString(0); debug_info += '\n'; } else if (statement.Succeeded()) { debug_info += "version: none\n"; } else { debug_info += "version: error\n"; } } else { debug_info += "version: invalid\n"; } debug_info += "schema:\n"; // sqlite_master has columns: // type - "index" or "table". // name - name of created element. // tbl_name - name of element, or target table in case of index. // rootpage - root page of the element in database file. // sql - SQL to create the element. // In general, the |sql| column is sufficient to derive the other // columns. |rootpage| is not interesting for debugging, without // the contents of the database. The COALESCE is because certain // automatic elements will have a |name| but no |sql|, const char* kSchemaSql = "SELECT COALESCE(sql, name) FROM sqlite_master"; sql::Statement statement(db->GetUniqueStatement(kSchemaSql)); while (statement.Step()) { debug_info += statement.ColumnString(0); debug_info += '\n'; } if (!statement.Succeeded()) debug_info += "error\n"; } // TODO(shess): Think of other things to log. Not logging the // statement text because the backtrace should suffice in most // cases. The database schema is a possibility, but the // likelihood of recursive error callbacks makes that risky (same // reasoning applies to other data fetched from the database). DumpWithoutCrashing2000(debug_info); } // TODO(shess): If this proves out, perhaps lift the code out to // chrome/browser/diagnostics/sqlite_diagnostics.{h,cc}. void GenerateDiagnostics(sql::Connection* db, size_t startup_kb, int extended_error) { int error = (extended_error & 0xFF); // Infrequently report information about the error up to the crash // server. static const uint64 kReportsPerMillion = 50000; // Since some/most errors will not resolve themselves, only report // once per Chrome run. static bool reported = false; if (reported) return; uint64 rand = base::RandGenerator(1000000); if (error == SQLITE_CORRUPT) { // Once the database is known to be corrupt, it will generate a // stream of errors until someone fixes it, so give one chance. // Set first in case of errors in generating the report. reported = true; // Corrupt cases currently dominate, report them very infrequently. static const uint64 kCorruptReportsPerMillion = 10000; if (rand < kCorruptReportsPerMillion) ReportCorrupt(db, startup_kb); } else if (error == SQLITE_READONLY) { // SQLITE_READONLY appears similar to SQLITE_CORRUPT - once it // is seen, it is almost guaranteed to be seen again. reported = true; if (rand < kReportsPerMillion) ReportError(db, extended_error); } else { // Only set the flag when making a report. This should allow // later (potentially different) errors in a stream of errors to // be reported. // // TODO(shess): Would it be worthwile to audit for which cases // want once-only handling? Sqlite.Error.Thumbnail shows // CORRUPT and READONLY as almost 95% of all reports on these // channels, so probably easier to just harvest from the field. if (rand < kReportsPerMillion) { reported = true; ReportError(db, extended_error); } } } // Create v5 schema for recovery code. bool InitSchemaV5(sql::Connection* db) { // This schema was derived from the strings used when v5 was in // force. The [favicons] index and the [icon_mapping] items were // copied from the current strings, after verifying that the // resulting schema exactly matches the schema created by the // original versions of those strings. This allows the linker to // share the strings if they match, while preferring correctness of // the current versions change. const char kFaviconsV5[] = "CREATE TABLE IF NOT EXISTS favicons(" "id INTEGER PRIMARY KEY," "url LONGVARCHAR NOT NULL," "last_updated INTEGER DEFAULT 0," "image_data BLOB," "icon_type INTEGER DEFAULT 1," "sizes LONGVARCHAR" ")"; const char kFaviconsIndexV5[] = "CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)"; if (!db->Execute(kFaviconsV5) || !db->Execute(kFaviconsIndexV5)) return false; const char kIconMappingV5[] = "CREATE TABLE IF NOT EXISTS icon_mapping" "(" "id INTEGER PRIMARY KEY," "page_url LONGVARCHAR NOT NULL," "icon_id INTEGER" ")"; const char kIconMappingUrlIndexV5[] = "CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx" " ON icon_mapping(page_url)"; const char kIconMappingIdIndexV5[] = "CREATE INDEX IF NOT EXISTS icon_mapping_icon_id_idx" " ON icon_mapping(icon_id)"; if (!db->Execute(kIconMappingV5) || !db->Execute(kIconMappingUrlIndexV5) || !db->Execute(kIconMappingIdIndexV5)) { return false; } return true; } // TODO(shess): Consider InitSchemaV7(). InitSchemaV5() is worthwhile // because there appear to be 10s of thousands of marooned v5 // databases in the wild. Once recovery reaches stable, the number of // corrupt-but-recoverable databases should drop, possibly to the // point where it is not worthwhile to maintain previous-version // recovery code. // TODO(shess): Alternately, think on a way to more cleanly represent // versioned schema going forward. bool InitTables(sql::Connection* db) { const char kIconMappingSql[] = "CREATE TABLE IF NOT EXISTS icon_mapping" "(" "id INTEGER PRIMARY KEY," "page_url LONGVARCHAR NOT NULL," "icon_id INTEGER" ")"; if (!db->Execute(kIconMappingSql)) return false; const char kFaviconsSql[] = "CREATE TABLE IF NOT EXISTS favicons" "(" "id INTEGER PRIMARY KEY," "url LONGVARCHAR NOT NULL," // default icon_type FAVICON to be consistent with past migration. "icon_type INTEGER DEFAULT 1" ")"; if (!db->Execute(kFaviconsSql)) return false; const char kFaviconBitmapsSql[] = "CREATE TABLE IF NOT EXISTS favicon_bitmaps" "(" "id INTEGER PRIMARY KEY," "icon_id INTEGER NOT NULL," "last_updated INTEGER DEFAULT 0," "image_data BLOB," "width INTEGER DEFAULT 0," "height INTEGER DEFAULT 0" ")"; if (!db->Execute(kFaviconBitmapsSql)) return false; return true; } bool InitIndices(sql::Connection* db) { const char kIconMappingUrlIndexSql[] = "CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx" " ON icon_mapping(page_url)"; const char kIconMappingIdIndexSql[] = "CREATE INDEX IF NOT EXISTS icon_mapping_icon_id_idx" " ON icon_mapping(icon_id)"; if (!db->Execute(kIconMappingUrlIndexSql) || !db->Execute(kIconMappingIdIndexSql)) { return false; } const char kFaviconsIndexSql[] = "CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)"; if (!db->Execute(kFaviconsIndexSql)) return false; const char kFaviconBitmapsIndexSql[] = "CREATE INDEX IF NOT EXISTS favicon_bitmaps_icon_id ON " "favicon_bitmaps(icon_id)"; if (!db->Execute(kFaviconBitmapsIndexSql)) return false; return true; } enum RecoveryEventType { RECOVERY_EVENT_RECOVERED = 0, RECOVERY_EVENT_FAILED_SCOPER, RECOVERY_EVENT_FAILED_META_VERSION_ERROR, // obsolete RECOVERY_EVENT_FAILED_META_VERSION_NONE, // obsolete RECOVERY_EVENT_FAILED_META_WRONG_VERSION6, // obsolete RECOVERY_EVENT_FAILED_META_WRONG_VERSION5, // obsolete RECOVERY_EVENT_FAILED_META_WRONG_VERSION, RECOVERY_EVENT_FAILED_RECOVER_META, // obsolete RECOVERY_EVENT_FAILED_META_INSERT, // obsolete RECOVERY_EVENT_FAILED_INIT, RECOVERY_EVENT_FAILED_RECOVER_FAVICONS, // obsolete RECOVERY_EVENT_FAILED_FAVICONS_INSERT, // obsolete RECOVERY_EVENT_FAILED_RECOVER_FAVICON_BITMAPS, // obsolete RECOVERY_EVENT_FAILED_FAVICON_BITMAPS_INSERT, // obsolete RECOVERY_EVENT_FAILED_RECOVER_ICON_MAPPING, // obsolete RECOVERY_EVENT_FAILED_ICON_MAPPING_INSERT, // obsolete RECOVERY_EVENT_RECOVERED_VERSION6, RECOVERY_EVENT_FAILED_META_INIT, RECOVERY_EVENT_FAILED_META_VERSION, RECOVERY_EVENT_DEPRECATED, RECOVERY_EVENT_FAILED_V5_INITSCHEMA, RECOVERY_EVENT_FAILED_V5_AUTORECOVER_FAVICONS, RECOVERY_EVENT_FAILED_V5_AUTORECOVER_ICON_MAPPING, RECOVERY_EVENT_RECOVERED_VERSION5, RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS, RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS, RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING, RECOVERY_EVENT_FAILED_COMMIT, // Always keep this at the end. RECOVERY_EVENT_MAX, }; void RecordRecoveryEvent(RecoveryEventType recovery_event) { UMA_HISTOGRAM_ENUMERATION("History.FaviconsRecovery", recovery_event, RECOVERY_EVENT_MAX); } // Recover the database to the extent possible, razing it if recovery // is not possible. // TODO(shess): This is mostly just a safe proof of concept. In the // real world, this database is probably not worthwhile recovering, as // opposed to just razing it and starting over whenever corruption is // detected. So this database is a good test subject. void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) { // NOTE(shess): This code is currently specific to the version // number. I am working on simplifying things to loosen the // dependency, meanwhile contact me if you need to bump the version. DCHECK_EQ(7, kCurrentVersionNumber); // TODO(shess): Reset back after? db->reset_error_callback(); // For histogram purposes. size_t favicons_rows_recovered = 0; size_t favicon_bitmaps_rows_recovered = 0; size_t icon_mapping_rows_recovered = 0; int64 original_size = 0; base::GetFileSize(db_path, &original_size); scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path); if (!recovery) { // TODO(shess): Unable to create recovery connection. This // implies something substantial is wrong. At this point |db| has // been poisoned so there is nothing really to do. // // Possible responses are unclear. If the failure relates to a // problem somehow specific to the temporary file used to back the // database, then an in-memory database could possibly be used. // This could potentially allow recovering the main database, and // might be simple to implement w/in Begin(). RecordRecoveryEvent(RECOVERY_EVENT_FAILED_SCOPER); return; } // Setup the meta recovery table and fetch the version number from // the corrupt database. int version = 0; if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) { // TODO(shess): Prior histograms indicate all failures are in // creating the recover virtual table for corrupt.meta. The table // may not exist, or the database may be too far gone. Either // way, unclear how to resolve. sql::Recovery::Rollback(recovery.Pass()); RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_VERSION); return; } // Recover v5 database to v5 schema. Next pass through Init() will // migrate to v7. if (version == 5) { sql::MetaTable recover_meta_table; if (!recover_meta_table.Init(recovery->db(), version, version)) { sql::Recovery::Rollback(recovery.Pass()); RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT); return; } // TODO(shess): These tests are separate for histogram purposes, // but once things look stable it can be tightened up. if (!InitSchemaV5(recovery->db())) { sql::Recovery::Rollback(recovery.Pass()); RecordRecoveryEvent(RECOVERY_EVENT_FAILED_V5_INITSCHEMA); return; } if (!recovery->AutoRecoverTable("favicons", 0, &favicons_rows_recovered)) { sql::Recovery::Rollback(recovery.Pass()); RecordRecoveryEvent(RECOVERY_EVENT_FAILED_V5_AUTORECOVER_FAVICONS); return; } if (!recovery->AutoRecoverTable("icon_mapping", 0, &icon_mapping_rows_recovered)) { sql::Recovery::Rollback(recovery.Pass()); RecordRecoveryEvent(RECOVERY_EVENT_FAILED_V5_AUTORECOVER_ICON_MAPPING); return; } ignore_result(sql::Recovery::Recovered(recovery.Pass())); // TODO(shess): Could this code be shared with the v6/7 code // without requiring too much state to be carried? // Track the size of the recovered database relative to the size of // the input database. The size should almost always be smaller, // unless the input database was empty to start with. If the // percentage results are very low, something is awry. int64 final_size = 0; if (original_size > 0 && base::GetFileSize(db_path, &final_size) && final_size > 0) { UMA_HISTOGRAM_PERCENTAGE("History.FaviconsRecoveredPercentage", final_size * 100 / original_size); } // Using 10,000 because these cases mostly care about "none // recovered" and "lots recovered". More than 10,000 rows recovered // probably means there's something wrong with the profile. UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFavicons", favicons_rows_recovered); UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsIconMapping", icon_mapping_rows_recovered); RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED_VERSION5); return; } // This code may be able to fetch versions that the regular // deprecation path cannot. if (version <= kDeprecatedVersionNumber) { sql::Recovery::Unrecoverable(recovery.Pass()); RecordRecoveryEvent(RECOVERY_EVENT_DEPRECATED); return; } // TODO(shess): Earlier versions have been handled or deprecated, // later versions should be impossible. Unrecoverable() seems // reasonable. if (version != 6 && version != 7) { RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION); sql::Recovery::Rollback(recovery.Pass()); return; } // Both v6 and v7 recover to current schema version. sql::MetaTable recover_meta_table; if (!recover_meta_table.Init(recovery->db(), kCurrentVersionNumber, kCompatibleVersionNumber)) { sql::Recovery::Rollback(recovery.Pass()); RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT); return; } // Create a fresh version of the database. The recovery code uses // conflict-resolution to handle duplicates, so the indices are // necessary. if (!InitTables(recovery->db()) || !InitIndices(recovery->db())) { // TODO(shess): Unable to create the new schema in the new // database. The new database should be a temporary file, so // being unable to work with it is pretty unclear. // // What are the potential responses, even? The recovery database // could be opened as in-memory. If the temp database had a // filesystem problem and the temp filesystem differs from the // main database, then that could fix it. sql::Recovery::Rollback(recovery.Pass()); RecordRecoveryEvent(RECOVERY_EVENT_FAILED_INIT); return; } // [favicons] differs because v6 had an unused [sizes] column which // was removed in v7. if (!recovery->AutoRecoverTable("favicons", 1, &favicons_rows_recovered)) { sql::Recovery::Rollback(recovery.Pass()); RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS); return; } if (!recovery->AutoRecoverTable("favicon_bitmaps", 0, &favicon_bitmaps_rows_recovered)) { sql::Recovery::Rollback(recovery.Pass()); RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS); return; } if (!recovery->AutoRecoverTable("icon_mapping", 0, &icon_mapping_rows_recovered)) { sql::Recovery::Rollback(recovery.Pass()); RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING); return; } // TODO(shess): Is it possible/likely to have broken foreign-key // issues with the tables? // - icon_mapping.icon_id maps to no favicons.id // - favicon_bitmaps.icon_id maps to no favicons.id // - favicons.id is referenced by no icon_mapping.icon_id // - favicons.id is referenced by no favicon_bitmaps.icon_id // This step is possibly not worth the effort necessary to develop // and sequence the statements, as it is basically a form of garbage // collection. if (!sql::Recovery::Recovered(recovery.Pass())) { RecordRecoveryEvent(RECOVERY_EVENT_FAILED_COMMIT); return; } // Track the size of the recovered database relative to the size of // the input database. The size should almost always be smaller, // unless the input database was empty to start with. If the // percentage results are very low, something is awry. int64 final_size = 0; if (original_size > 0 && base::GetFileSize(db_path, &final_size) && final_size > 0) { int percentage = static_cast<int>(original_size * 100 / final_size); UMA_HISTOGRAM_PERCENTAGE("History.FaviconsRecoveredPercentage", std::max(100, percentage)); } // Using 10,000 because these cases mostly care about "none // recovered" and "lots recovered". More than 10,000 rows recovered // probably means there's something wrong with the profile. UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFavicons", favicons_rows_recovered); UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFaviconBitmaps", favicon_bitmaps_rows_recovered); UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsIconMapping", icon_mapping_rows_recovered); if (version == 6) { RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED_VERSION6); } else { RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED); } } void DatabaseErrorCallback(sql::Connection* db, const base::FilePath& db_path, size_t startup_kb, int extended_error, sql::Statement* stmt) { // TODO(shess): Assert that this is running on a safe thread. // AFAICT, should be the history thread, but at this level I can't // see how to reach that. // TODO(shess): For now, don't report on beta or stable so as not to // overwhelm the crash server. Once the big fish are fried, // consider reporting at a reduced rate on the bigger channels. chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel(); if (channel != chrome::VersionInfo::CHANNEL_STABLE && channel != chrome::VersionInfo::CHANNEL_BETA) { GenerateDiagnostics(db, startup_kb, extended_error); } // Attempt to recover corrupt databases. int error = (extended_error & 0xFF); if (error == SQLITE_CORRUPT || error == SQLITE_CANTOPEN || error == SQLITE_NOTADB) { RecoverDatabaseOrRaze(db, db_path); } // The default handling is to assert on debug and to ignore on release. if (!sql::Connection::ShouldIgnoreSqliteError(extended_error)) DLOG(FATAL) << db->GetErrorMessage(); } } // namespace namespace history { ThumbnailDatabase::IconMappingEnumerator::IconMappingEnumerator() { } ThumbnailDatabase::IconMappingEnumerator::~IconMappingEnumerator() { } bool ThumbnailDatabase::IconMappingEnumerator::GetNextIconMapping( IconMapping* icon_mapping) { if (!statement_.Step()) return false; FillIconMapping(statement_, GURL(statement_.ColumnString(4)), icon_mapping); return true; } ThumbnailDatabase::ThumbnailDatabase() { } ThumbnailDatabase::~ThumbnailDatabase() { // The DBCloseScoper will delete the DB and the cache. } sql::InitStatus ThumbnailDatabase::Init(const base::FilePath& db_name) { // TODO(shess): Consider separating database open from schema setup. // With that change, this code could Raze() from outside the // transaction, rather than needing RazeAndClose() in InitImpl(). // Retry failed setup in case the recovery system fixed things. const size_t kAttempts = 2; sql::InitStatus status = sql::INIT_FAILURE; for (size_t i = 0; i < kAttempts; ++i) { status = InitImpl(db_name); if (status == sql::INIT_OK) return status; meta_table_.Reset(); db_.Close(); } return status; } void ThumbnailDatabase::ComputeDatabaseMetrics() { sql::Statement favicon_count( db_.GetCachedStatement(SQL_FROM_HERE, "SELECT COUNT(*) FROM favicons")); UMA_HISTOGRAM_COUNTS_10000( "History.NumFaviconsInDB", favicon_count.Step() ? favicon_count.ColumnInt(0) : 0); } void ThumbnailDatabase::BeginTransaction() { db_.BeginTransaction(); } void ThumbnailDatabase::CommitTransaction() { db_.CommitTransaction(); } void ThumbnailDatabase::RollbackTransaction() { db_.RollbackTransaction(); } void ThumbnailDatabase::Vacuum() { DCHECK(db_.transaction_nesting() == 0) << "Can not have a transaction when vacuuming."; ignore_result(db_.Execute("VACUUM")); } void ThumbnailDatabase::TrimMemory(bool aggressively) { db_.TrimMemory(aggressively); } bool ThumbnailDatabase::GetFaviconBitmapIDSizes( chrome::FaviconID icon_id, std::vector<FaviconBitmapIDSize>* bitmap_id_sizes) { DCHECK(icon_id); sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "SELECT id, width, height FROM favicon_bitmaps WHERE icon_id=?")); statement.BindInt64(0, icon_id); bool result = false; while (statement.Step()) { result = true; if (!bitmap_id_sizes) return result; FaviconBitmapIDSize bitmap_id_size; bitmap_id_size.bitmap_id = statement.ColumnInt64(0); bitmap_id_size.pixel_size = gfx::Size(statement.ColumnInt(1), statement.ColumnInt(2)); bitmap_id_sizes->push_back(bitmap_id_size); } return result; } bool ThumbnailDatabase::GetFaviconBitmaps( chrome::FaviconID icon_id, std::vector<FaviconBitmap>* favicon_bitmaps) { DCHECK(icon_id); sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "SELECT id, last_updated, image_data, width, height FROM favicon_bitmaps " "WHERE icon_id=?")); statement.BindInt64(0, icon_id); bool result = false; while (statement.Step()) { result = true; if (!favicon_bitmaps) return result; FaviconBitmap favicon_bitmap; favicon_bitmap.bitmap_id = statement.ColumnInt64(0); favicon_bitmap.icon_id = icon_id; favicon_bitmap.last_updated = base::Time::FromInternalValue(statement.ColumnInt64(1)); if (statement.ColumnByteLength(2) > 0) { scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); statement.ColumnBlobAsVector(2, &data->data()); favicon_bitmap.bitmap_data = data; } favicon_bitmap.pixel_size = gfx::Size(statement.ColumnInt(3), statement.ColumnInt(4)); favicon_bitmaps->push_back(favicon_bitmap); } return result; } bool ThumbnailDatabase::GetFaviconBitmap( FaviconBitmapID bitmap_id, base::Time* last_updated, scoped_refptr<base::RefCountedMemory>* png_icon_data, gfx::Size* pixel_size) { DCHECK(bitmap_id); sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "SELECT last_updated, image_data, width, height FROM favicon_bitmaps " "WHERE id=?")); statement.BindInt64(0, bitmap_id); if (!statement.Step()) return false; if (last_updated) *last_updated = base::Time::FromInternalValue(statement.ColumnInt64(0)); if (png_icon_data && statement.ColumnByteLength(1) > 0) { scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); statement.ColumnBlobAsVector(1, &data->data()); *png_icon_data = data; } if (pixel_size) { *pixel_size = gfx::Size(statement.ColumnInt(2), statement.ColumnInt(3)); } return true; } FaviconBitmapID ThumbnailDatabase::AddFaviconBitmap( chrome::FaviconID icon_id, const scoped_refptr<base::RefCountedMemory>& icon_data, base::Time time, const gfx::Size& pixel_size) { DCHECK(icon_id); sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "INSERT INTO favicon_bitmaps (icon_id, image_data, last_updated, width, " "height) VALUES (?, ?, ?, ?, ?)")); statement.BindInt64(0, icon_id); if (icon_data.get() && icon_data->size()) { statement.BindBlob(1, icon_data->front(), static_cast<int>(icon_data->size())); } else { statement.BindNull(1); } statement.BindInt64(2, time.ToInternalValue()); statement.BindInt(3, pixel_size.width()); statement.BindInt(4, pixel_size.height()); if (!statement.Run()) return 0; return db_.GetLastInsertRowId(); } bool ThumbnailDatabase::SetFaviconBitmap( FaviconBitmapID bitmap_id, scoped_refptr<base::RefCountedMemory> bitmap_data, base::Time time) { DCHECK(bitmap_id); sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "UPDATE favicon_bitmaps SET image_data=?, last_updated=? WHERE id=?")); if (bitmap_data.get() && bitmap_data->size()) { statement.BindBlob(0, bitmap_data->front(), static_cast<int>(bitmap_data->size())); } else { statement.BindNull(0); } statement.BindInt64(1, time.ToInternalValue()); statement.BindInt64(2, bitmap_id); return statement.Run(); } bool ThumbnailDatabase::SetFaviconBitmapLastUpdateTime( FaviconBitmapID bitmap_id, base::Time time) { DCHECK(bitmap_id); sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "UPDATE favicon_bitmaps SET last_updated=? WHERE id=?")); statement.BindInt64(0, time.ToInternalValue()); statement.BindInt64(1, bitmap_id); return statement.Run(); } bool ThumbnailDatabase::DeleteFaviconBitmap(FaviconBitmapID bitmap_id) { sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "DELETE FROM favicon_bitmaps WHERE id=?")); statement.BindInt64(0, bitmap_id); return statement.Run(); } bool ThumbnailDatabase::SetFaviconOutOfDate(chrome::FaviconID icon_id) { sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "UPDATE favicon_bitmaps SET last_updated=? WHERE icon_id=?")); statement.BindInt64(0, 0); statement.BindInt64(1, icon_id); return statement.Run(); } chrome::FaviconID ThumbnailDatabase::GetFaviconIDForFaviconURL( const GURL& icon_url, int required_icon_type, chrome::IconType* icon_type) { sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "SELECT id, icon_type FROM favicons WHERE url=? AND (icon_type & ? > 0) " "ORDER BY icon_type DESC")); statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url)); statement.BindInt(1, required_icon_type); if (!statement.Step()) return 0; // not cached if (icon_type) *icon_type = static_cast<chrome::IconType>(statement.ColumnInt(1)); return statement.ColumnInt64(0); } bool ThumbnailDatabase::GetFaviconHeader(chrome::FaviconID icon_id, GURL* icon_url, chrome::IconType* icon_type) { DCHECK(icon_id); sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "SELECT url, icon_type FROM favicons WHERE id=?")); statement.BindInt64(0, icon_id); if (!statement.Step()) return false; // No entry for the id. if (icon_url) *icon_url = GURL(statement.ColumnString(0)); if (icon_type) *icon_type = static_cast<chrome::IconType>(statement.ColumnInt(1)); return true; } chrome::FaviconID ThumbnailDatabase::AddFavicon( const GURL& icon_url, chrome::IconType icon_type) { sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "INSERT INTO favicons (url, icon_type) VALUES (?, ?)")); statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url)); statement.BindInt(1, icon_type); if (!statement.Run()) return 0; return db_.GetLastInsertRowId(); } chrome::FaviconID ThumbnailDatabase::AddFavicon( const GURL& icon_url, chrome::IconType icon_type, const scoped_refptr<base::RefCountedMemory>& icon_data, base::Time time, const gfx::Size& pixel_size) { chrome::FaviconID icon_id = AddFavicon(icon_url, icon_type); if (!icon_id || !AddFaviconBitmap(icon_id, icon_data, time, pixel_size)) return 0; return icon_id; } bool ThumbnailDatabase::DeleteFavicon(chrome::FaviconID id) { sql::Statement statement; statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, "DELETE FROM favicons WHERE id = ?")); statement.BindInt64(0, id); if (!statement.Run()) return false; statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, "DELETE FROM favicon_bitmaps WHERE icon_id = ?")); statement.BindInt64(0, id); return statement.Run(); } bool ThumbnailDatabase::GetIconMappingsForPageURL( const GURL& page_url, int required_icon_types, std::vector<IconMapping>* filtered_mapping_data) { std::vector<IconMapping> mapping_data; if (!GetIconMappingsForPageURL(page_url, &mapping_data)) return false; bool result = false; for (std::vector<IconMapping>::iterator m = mapping_data.begin(); m != mapping_data.end(); ++m) { if (m->icon_type & required_icon_types) { result = true; if (!filtered_mapping_data) return result; // Restrict icon type of subsequent matches to |m->icon_type|. // |m->icon_type| is the largest IconType in |mapping_data| because // |mapping_data| is sorted in descending order of IconType. required_icon_types = m->icon_type; filtered_mapping_data->push_back(*m); } } return result; } bool ThumbnailDatabase::GetIconMappingsForPageURL( const GURL& page_url, std::vector<IconMapping>* mapping_data) { sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, " "favicons.url " "FROM icon_mapping " "INNER JOIN favicons " "ON icon_mapping.icon_id = favicons.id " "WHERE icon_mapping.page_url=? " "ORDER BY favicons.icon_type DESC")); statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); bool result = false; while (statement.Step()) { result = true; if (!mapping_data) return result; IconMapping icon_mapping; FillIconMapping(statement, page_url, &icon_mapping); mapping_data->push_back(icon_mapping); } return result; } IconMappingID ThumbnailDatabase::AddIconMapping(const GURL& page_url, chrome::FaviconID icon_id) { const char kSql[] = "INSERT INTO icon_mapping (page_url, icon_id) VALUES (?, ?)"; sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSql)); statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); statement.BindInt64(1, icon_id); if (!statement.Run()) return 0; return db_.GetLastInsertRowId(); } bool ThumbnailDatabase::UpdateIconMapping(IconMappingID mapping_id, chrome::FaviconID icon_id) { sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "UPDATE icon_mapping SET icon_id=? WHERE id=?")); statement.BindInt64(0, icon_id); statement.BindInt64(1, mapping_id); return statement.Run(); } bool ThumbnailDatabase::DeleteIconMappings(const GURL& page_url) { sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "DELETE FROM icon_mapping WHERE page_url = ?")); statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); return statement.Run(); } bool ThumbnailDatabase::DeleteIconMapping(IconMappingID mapping_id) { sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "DELETE FROM icon_mapping WHERE id=?")); statement.BindInt64(0, mapping_id); return statement.Run(); } bool ThumbnailDatabase::HasMappingFor(chrome::FaviconID id) { sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "SELECT id FROM icon_mapping " "WHERE icon_id=?")); statement.BindInt64(0, id); return statement.Step(); } bool ThumbnailDatabase::CloneIconMappings(const GURL& old_page_url, const GURL& new_page_url) { sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, "SELECT icon_id FROM icon_mapping " "WHERE page_url=?")); if (!statement.is_valid()) return false; // Do nothing if there are existing bindings statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url)); if (statement.Step()) return true; statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, "INSERT INTO icon_mapping (page_url, icon_id) " "SELECT ?, icon_id FROM icon_mapping " "WHERE page_url = ?")); statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url)); statement.BindString(1, URLDatabase::GURLToDatabaseURL(old_page_url)); return statement.Run(); } bool ThumbnailDatabase::InitIconMappingEnumerator( chrome::IconType type, IconMappingEnumerator* enumerator) { DCHECK(!enumerator->statement_.is_valid()); enumerator->statement_.Assign(db_.GetCachedStatement( SQL_FROM_HERE, "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, " "favicons.url, icon_mapping.page_url " "FROM icon_mapping JOIN favicons ON (" "icon_mapping.icon_id = favicons.id) " "WHERE favicons.icon_type = ?")); enumerator->statement_.BindInt(0, type); return enumerator->statement_.is_valid(); } bool ThumbnailDatabase::RetainDataForPageUrls( const std::vector<GURL>& urls_to_keep) { sql::Transaction transaction(&db_); if (!transaction.Begin()) return false; // temp.icon_id_mapping generates new icon ids as consecutive // integers starting from 1, and maps them to the old icon ids. { const char kIconMappingCreate[] = "CREATE TEMP TABLE icon_id_mapping " "(" "new_icon_id INTEGER PRIMARY KEY," "old_icon_id INTEGER NOT NULL UNIQUE" ")"; if (!db_.Execute(kIconMappingCreate)) return false; // Insert the icon ids for retained urls, skipping duplicates. const char kIconMappingSql[] = "INSERT OR IGNORE INTO temp.icon_id_mapping (old_icon_id) " "SELECT icon_id FROM icon_mapping WHERE page_url = ?"; sql::Statement statement(db_.GetUniqueStatement(kIconMappingSql)); for (std::vector<GURL>::const_iterator i = urls_to_keep.begin(); i != urls_to_keep.end(); ++i) { statement.BindString(0, URLDatabase::GURLToDatabaseURL(*i)); if (!statement.Run()) return false; statement.Reset(true); } } const char kRenameIconMappingTable[] = "ALTER TABLE icon_mapping RENAME TO old_icon_mapping"; const char kCopyIconMapping[] = "INSERT INTO icon_mapping (page_url, icon_id) " "SELECT old.page_url, mapping.new_icon_id " "FROM old_icon_mapping AS old " "JOIN temp.icon_id_mapping AS mapping " "ON (old.icon_id = mapping.old_icon_id)"; const char kDropOldIconMappingTable[] = "DROP TABLE old_icon_mapping"; const char kRenameFaviconsTable[] = "ALTER TABLE favicons RENAME TO old_favicons"; const char kCopyFavicons[] = "INSERT INTO favicons (id, url, icon_type) " "SELECT mapping.new_icon_id, old.url, old.icon_type " "FROM old_favicons AS old " "JOIN temp.icon_id_mapping AS mapping " "ON (old.id = mapping.old_icon_id)"; const char kDropOldFaviconsTable[] = "DROP TABLE old_favicons"; const char kRenameFaviconBitmapsTable[] = "ALTER TABLE favicon_bitmaps RENAME TO old_favicon_bitmaps"; const char kCopyFaviconBitmaps[] = "INSERT INTO favicon_bitmaps " " (icon_id, last_updated, image_data, width, height) " "SELECT mapping.new_icon_id, old.last_updated, " " old.image_data, old.width, old.height " "FROM old_favicon_bitmaps AS old " "JOIN temp.icon_id_mapping AS mapping " "ON (old.icon_id = mapping.old_icon_id)"; const char kDropOldFaviconBitmapsTable[] = "DROP TABLE old_favicon_bitmaps"; // Rename existing tables to new location. if (!db_.Execute(kRenameIconMappingTable) || !db_.Execute(kRenameFaviconsTable) || !db_.Execute(kRenameFaviconBitmapsTable)) { return false; } // Initialize the replacement tables. At this point the old indices // still exist (pointing to the old_* tables), so do not initialize // the indices. if (!InitTables(&db_)) return false; // Copy all of the data over. if (!db_.Execute(kCopyIconMapping) || !db_.Execute(kCopyFavicons) || !db_.Execute(kCopyFaviconBitmaps)) { return false; } // Drop the old_* tables, which also drops the indices. if (!db_.Execute(kDropOldIconMappingTable) || !db_.Execute(kDropOldFaviconsTable) || !db_.Execute(kDropOldFaviconBitmapsTable)) { return false; } // Recreate the indices. // TODO(shess): UNIQUE indices could fail due to duplication. This // could happen in case of corruption. if (!InitIndices(&db_)) return false; const char kIconMappingDrop[] = "DROP TABLE temp.icon_id_mapping"; if (!db_.Execute(kIconMappingDrop)) return false; return transaction.Commit(); } sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db, const base::FilePath& db_name) { size_t startup_kb = 0; int64 size_64; if (base::GetFileSize(db_name, &size_64)) startup_kb = static_cast<size_t>(size_64 / 1024); db->set_histogram_tag("Thumbnail"); db->set_error_callback(base::Bind(&DatabaseErrorCallback, db, db_name, startup_kb)); // Thumbnails db now only stores favicons, so we don't need that big a page // size or cache. db->set_page_size(2048); db->set_cache_size(32); // Run the database in exclusive mode. Nobody else should be accessing the // database while we're running, and this will give somewhat improved perf. db->set_exclusive_locking(); if (!db->Open(db_name)) return sql::INIT_FAILURE; return sql::INIT_OK; } sql::InitStatus ThumbnailDatabase::InitImpl(const base::FilePath& db_name) { sql::InitStatus status = OpenDatabase(&db_, db_name); if (status != sql::INIT_OK) return status; // Clear databases which are too old to process. DCHECK_LT(kDeprecatedVersionNumber, kCurrentVersionNumber); sql::MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersionNumber); // TODO(shess): Sqlite.Version.Thumbnail shows versions 22, 23, and // 25. Future versions are not destroyed because that could lead to // data loss if the profile is opened by a later channel, but // perhaps a heuristic like >kCurrentVersionNumber+3 could be used. // Scope initialization in a transaction so we can't be partially initialized. sql::Transaction transaction(&db_); if (!transaction.Begin()) return sql::INIT_FAILURE; // TODO(shess): Failing Begin() implies that something serious is // wrong with the database. Raze() may be in order. #if defined(OS_MACOSX) // Exclude the thumbnails file from backups. base::mac::SetFileBackupExclusion(db_name); #endif // thumbnails table has been obsolete for a long time, remove any // detrious. ignore_result(db_.Execute("DROP TABLE IF EXISTS thumbnails")); // At some point, operations involving temporary tables weren't done // atomically and users have been stranded. Drop those tables and // move on. // TODO(shess): Prove it? Audit all cases and see if it's possible // that this implies non-atomic update, and should thus be handled // via the corruption handler. ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_favicons")); ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_favicon_bitmaps")); ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_icon_mapping")); // Create the tables. if (!meta_table_.Init(&db_, kCurrentVersionNumber, kCompatibleVersionNumber) || !InitTables(&db_) || !InitIndices(&db_)) { return sql::INIT_FAILURE; } // Version check. We should not encounter a database too old for us to handle // in the wild, so we try to continue in that case. if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) { LOG(WARNING) << "Thumbnail database is too new."; return sql::INIT_TOO_NEW; } int cur_version = meta_table_.GetVersionNumber(); if (!db_.DoesColumnExist("favicons", "icon_type")) { LOG(ERROR) << "Raze because of missing favicon.icon_type"; RecordInvalidStructure(STRUCTURE_EVENT_VERSION4); db_.RazeAndClose(); return sql::INIT_FAILURE; } if (cur_version < 7 && !db_.DoesColumnExist("favicons", "sizes")) { LOG(ERROR) << "Raze because of missing favicon.sizes"; RecordInvalidStructure(STRUCTURE_EVENT_VERSION5); db_.RazeAndClose(); return sql::INIT_FAILURE; } if (cur_version == 5) { ++cur_version; if (!UpgradeToVersion6()) return CantUpgradeToVersion(cur_version); } if (cur_version == 6) { ++cur_version; if (!UpgradeToVersion7()) return CantUpgradeToVersion(cur_version); } LOG_IF(WARNING, cur_version < kCurrentVersionNumber) << "Thumbnail database version " << cur_version << " is too old to handle."; // Initialization is complete. if (!transaction.Commit()) return sql::INIT_FAILURE; // Raze the database if the structure of the favicons database is not what // it should be. This error cannot be detected via the SQL error code because // the error code for running SQL statements against a database with missing // columns is SQLITE_ERROR which is not unique enough to act upon. // TODO(pkotwicz): Revisit this in M27 and see if the razing can be removed. // (crbug.com/166453) if (IsFaviconDBStructureIncorrect()) { LOG(ERROR) << "Raze because of invalid favicon db structure."; RecordInvalidStructure(STRUCTURE_EVENT_FAVICON); db_.RazeAndClose(); return sql::INIT_FAILURE; } return sql::INIT_OK; } sql::InitStatus ThumbnailDatabase::CantUpgradeToVersion(int cur_version) { LOG(WARNING) << "Unable to update to thumbnail database to version " << cur_version << "."; db_.Close(); return sql::INIT_FAILURE; } bool ThumbnailDatabase::UpgradeToVersion6() { // Move bitmap data from favicons to favicon_bitmaps. bool success = db_.Execute("INSERT INTO favicon_bitmaps (icon_id, last_updated, " "image_data, width, height)" "SELECT id, last_updated, image_data, 0, 0 FROM favicons") && db_.Execute("CREATE TABLE temp_favicons (" "id INTEGER PRIMARY KEY," "url LONGVARCHAR NOT NULL," "icon_type INTEGER DEFAULT 1," // default icon_type FAVICON to be consistent with // past migration. "sizes LONGVARCHAR)") && db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) " "SELECT id, url, icon_type FROM favicons") && db_.Execute("DROP TABLE favicons") && db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons"); // NOTE(shess): v7 will re-create the index. if (!success) return false; meta_table_.SetVersionNumber(6); meta_table_.SetCompatibleVersionNumber(std::min(6, kCompatibleVersionNumber)); return true; } bool ThumbnailDatabase::UpgradeToVersion7() { // Sizes column was never used, remove it. bool success = db_.Execute("CREATE TABLE temp_favicons (" "id INTEGER PRIMARY KEY," "url LONGVARCHAR NOT NULL," // default icon_type FAVICON to be consistent with // past migration. "icon_type INTEGER DEFAULT 1)") && db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) " "SELECT id, url, icon_type FROM favicons") && db_.Execute("DROP TABLE favicons") && db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons") && db_.Execute("CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)"); if (!success) return false; meta_table_.SetVersionNumber(7); meta_table_.SetCompatibleVersionNumber(std::min(7, kCompatibleVersionNumber)); return true; } bool ThumbnailDatabase::IsFaviconDBStructureIncorrect() { return !db_.IsSQLValid("SELECT id, url, icon_type FROM favicons"); } } // namespace history