| // 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/debug/dump_without_crashing.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 "components/history/core/browser/history_client.h" |
| #include "components/history/core/browser/url_database.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<favicon_base::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); |
| |
| base::debug::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); |
| } |
| } |
| } |
| |
| // NOTE(shess): Schema modifications must consider initial creation in |
| // |InitImpl()|, recovery in |RecoverDatabaseOrRaze()|, and history pruning in |
| // |RetainDataForPageUrls()|. |
| 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; |
| } |
| |
| // NOTE(shess): Schema modifications must consider initial creation in |
| // |InitImpl()|, recovery in |RecoverDatabaseOrRaze()|, and history pruning in |
| // |RetainDataForPageUrls()|. |
| 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, // obsolete |
| RECOVERY_EVENT_FAILED_META_INIT, |
| RECOVERY_EVENT_FAILED_META_VERSION, |
| RECOVERY_EVENT_DEPRECATED, |
| RECOVERY_EVENT_FAILED_V5_INITSCHEMA, // obsolete |
| RECOVERY_EVENT_FAILED_V5_AUTORECOVER_FAVICONS, // obsolete |
| RECOVERY_EVENT_FAILED_V5_AUTORECOVER_ICON_MAPPING, // obsolete |
| RECOVERY_EVENT_RECOVERED_VERSION5, // obsolete |
| 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; |
| } |
| |
| // This code may be able to fetch version information that the regular |
| // deprecation path cannot. |
| // NOTE(shess): v5 and v6 are currently not deprecated in the normal Init() |
| // path, but are deprecated in the recovery path in the interest of keeping |
| // the code simple. http://crbug.com/327485 for numbers. |
| DCHECK_LE(kDeprecatedVersionNumber, 6); |
| if (version <= 6) { |
| sql::Recovery::Unrecoverable(recovery.Pass()); |
| RecordRecoveryEvent(RECOVERY_EVENT_DEPRECATED); |
| return; |
| } |
| |
| // Earlier versions have been handled or deprecated, later versions should be |
| // impossible. |
| if (version != 7) { |
| sql::Recovery::Unrecoverable(recovery.Pass()); |
| RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION); |
| return; |
| } |
| |
| // 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; |
| } |
| |
| if (!recovery->AutoRecoverTable("favicons", 0, &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); |
| |
| RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED); |
| } |
| |
| void DatabaseErrorCallback(sql::Connection* db, |
| const base::FilePath& db_path, |
| size_t startup_kb, |
| history::HistoryClient* history_client, |
| 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. |
| |
| if (history_client && history_client->ShouldReportDatabaseError()) { |
| 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(HistoryClient* history_client) |
| : history_client_(history_client) { |
| } |
| |
| 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( |
| 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 ThumbnailDatabase::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 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( |
| favicon_base::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(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(); |
| } |
| |
| favicon_base::FaviconID ThumbnailDatabase::GetFaviconIDForFaviconURL( |
| const GURL& icon_url, |
| int required_icon_type, |
| favicon_base::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<favicon_base::IconType>(statement.ColumnInt(1)); |
| return statement.ColumnInt64(0); |
| } |
| |
| bool ThumbnailDatabase::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 = static_cast<favicon_base::IconType>(statement.ColumnInt(1)); |
| |
| return true; |
| } |
| |
| favicon_base::FaviconID ThumbnailDatabase::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, URLDatabase::GURLToDatabaseURL(icon_url)); |
| statement.BindInt(1, icon_type); |
| |
| if (!statement.Run()) |
| return 0; |
| return db_.GetLastInsertRowId(); |
| } |
| |
| favicon_base::FaviconID ThumbnailDatabase::AddFavicon( |
| const GURL& icon_url, |
| favicon_base::IconType icon_type, |
| const scoped_refptr<base::RefCountedMemory>& icon_data, |
| 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, time, pixel_size)) |
| return 0; |
| |
| return icon_id; |
| } |
| |
| bool ThumbnailDatabase::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 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, |
| favicon_base::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, |
| favicon_base::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(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(); |
| } |
| |
| 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( |
| 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, 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, history_client_)); |
| |
| // 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 |