| Index: trunk/src/chrome/browser/history/thumbnail_database.cc
|
| ===================================================================
|
| --- trunk/src/chrome/browser/history/thumbnail_database.cc (revision 235603)
|
| +++ trunk/src/chrome/browser/history/thumbnail_database.cc (working copy)
|
| @@ -275,6 +275,60 @@
|
| }
|
| }
|
|
|
| +// 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"
|
| @@ -342,22 +396,31 @@
|
| enum RecoveryEventType {
|
| RECOVERY_EVENT_RECOVERED = 0,
|
| RECOVERY_EVENT_FAILED_SCOPER,
|
| - RECOVERY_EVENT_FAILED_META_VERSION_ERROR,
|
| - RECOVERY_EVENT_FAILED_META_VERSION_NONE,
|
| + 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,
|
| + RECOVERY_EVENT_FAILED_META_WRONG_VERSION5, // obsolete
|
| RECOVERY_EVENT_FAILED_META_WRONG_VERSION,
|
| - RECOVERY_EVENT_FAILED_RECOVER_META,
|
| + RECOVERY_EVENT_FAILED_RECOVER_META, // obsolete
|
| RECOVERY_EVENT_FAILED_META_INSERT, // obsolete
|
| RECOVERY_EVENT_FAILED_INIT,
|
| - RECOVERY_EVENT_FAILED_RECOVER_FAVICONS,
|
| - RECOVERY_EVENT_FAILED_FAVICONS_INSERT,
|
| - RECOVERY_EVENT_FAILED_RECOVER_FAVICON_BITMAPS,
|
| - RECOVERY_EVENT_FAILED_FAVICON_BITMAPS_INSERT,
|
| - RECOVERY_EVENT_FAILED_RECOVER_ICON_MAPPING,
|
| - RECOVERY_EVENT_FAILED_ICON_MAPPING_INSERT,
|
| + 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,
|
|
|
| // Always keep this at the end.
|
| RECOVERY_EVENT_MAX,
|
| @@ -405,77 +468,106 @@
|
| return;
|
| }
|
|
|
| - // Setup the meta recovery table, and check that the version number
|
| - // is covered by the recovery code.
|
| - // TODO(shess): sql::Recovery should provide a helper to handle meta.
|
| - int version = 0; // For reporting which version was recovered.
|
| - {
|
| - const char kRecoverySql[] =
|
| - "CREATE VIRTUAL TABLE temp.recover_meta USING recover"
|
| - "("
|
| - "corrupt.meta,"
|
| - "key TEXT NOT NULL,"
|
| - "value TEXT" // Really? Never int?
|
| - ")";
|
| - if (!recovery->db()->Execute(kRecoverySql)) {
|
| - // TODO(shess): Failure to create the recover_meta table could
|
| - // mean that the main database is too corrupt to access, or that
|
| - // the meta table doesn't exist.
|
| + // 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_RECOVER_META);
|
| + RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT);
|
| return;
|
| }
|
|
|
| - {
|
| - const char kRecoveryVersionSql[] =
|
| - "SELECT value FROM recover_meta WHERE key = 'version'";
|
| - sql::Statement recovery_version(
|
| - recovery->db()->GetUniqueStatement(kRecoveryVersionSql));
|
| - if (!recovery_version.Step()) {
|
| - if (!recovery_version.Succeeded()) {
|
| - RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_VERSION_ERROR);
|
| - // TODO(shess): An error while processing the statement is
|
| - // probably not recoverable.
|
| - } else {
|
| - RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_VERSION_NONE);
|
| - // TODO(shess): If a positive version lock cannot be achieved,
|
| - // the database could still be recovered by optimistically
|
| - // attempting to copy things. In the limit, the schema found
|
| - // could be inspected. Less clear is whether optimistic
|
| - // recovery really makes sense.
|
| - }
|
| - recovery_version.Clear();
|
| - sql::Recovery::Rollback(recovery.Pass());
|
| - return;
|
| - }
|
| - version = recovery_version.ColumnInt(0);
|
| + // 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;
|
| + }
|
|
|
| - // Recovery code is generally schema-dependent. Version 7 and
|
| - // version 6 are very similar, so can be handled together.
|
| - // Track version 5, to see whether it's worth writing recovery
|
| - // code for.
|
| - if (version != 7 && version != 6) {
|
| - if (version == 5) {
|
| - RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION5);
|
| - } else {
|
| - RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION);
|
| - }
|
| - recovery_version.Clear();
|
| - sql::Recovery::Rollback(recovery.Pass());
|
| - return;
|
| - }
|
| + if (!recovery->AutoRecoverTable("favicons", 0, &favicons_rows_recovered)) {
|
| + sql::Recovery::Rollback(recovery.Pass());
|
| + RecordRecoveryEvent(RECOVERY_EVENT_FAILED_V5_AUTORECOVER_FAVICONS);
|
| + return;
|
| }
|
|
|
| - // Either version 6 or version 7 recovers to current.
|
| - sql::MetaTable recover_meta_table;
|
| - if (!recover_meta_table.Init(recovery->db(), kCurrentVersionNumber,
|
| - kCompatibleVersionNumber)) {
|
| + if (!recovery->AutoRecoverTable("icon_mapping", 0,
|
| + &icon_mapping_rows_recovered)) {
|
| sql::Recovery::Rollback(recovery.Pass());
|
| - RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT);
|
| + 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 &&
|
| + file_util::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.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.
|
| @@ -493,114 +585,24 @@
|
| return;
|
| }
|
|
|
| - // Setup favicons table.
|
| - {
|
| - // Version 6 had the |sizes| column, version 7 removed it. The
|
| - // recover virtual table treats more columns than expected as an
|
| - // error, but if _fewer_ columns are present, they can be treated
|
| - // as NULL. SQLite requires this because ALTER TABLE adds columns
|
| - // to the schema, but not to the actual table storage.
|
| - const char kRecoverySql[] =
|
| - "CREATE VIRTUAL TABLE temp.recover_favicons USING recover"
|
| - "("
|
| - "corrupt.favicons,"
|
| - "id ROWID,"
|
| - "url TEXT NOT NULL,"
|
| - "icon_type INTEGER,"
|
| - "sizes TEXT"
|
| - ")";
|
| - if (!recovery->db()->Execute(kRecoverySql)) {
|
| - // TODO(shess): Failure to create the recovery table probably
|
| - // means unrecoverable.
|
| - sql::Recovery::Rollback(recovery.Pass());
|
| - RecordRecoveryEvent(RECOVERY_EVENT_FAILED_RECOVER_FAVICONS);
|
| - return;
|
| - }
|
| -
|
| - // TODO(shess): Check if the DEFAULT 1 will just cover the
|
| - // COALESCE(). Either way, the new code has a literal 1 rather
|
| - // than a NULL, right?
|
| - const char kCopySql[] =
|
| - "INSERT OR REPLACE INTO main.favicons "
|
| - "SELECT id, url, COALESCE(icon_type, 1) FROM recover_favicons";
|
| - if (!recovery->db()->Execute(kCopySql)) {
|
| - // TODO(shess): The recover_favicons table should mask problems
|
| - // with the source file, so this implies failure to write to the
|
| - // recovery database.
|
| - sql::Recovery::Rollback(recovery.Pass());
|
| - RecordRecoveryEvent(RECOVERY_EVENT_FAILED_FAVICONS_INSERT);
|
| - return;
|
| - }
|
| - favicons_rows_recovered = recovery->db()->GetLastChangeCount();
|
| + // [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;
|
| }
|
| -
|
| - // Setup favicons_bitmaps table.
|
| - {
|
| - const char kRecoverySql[] =
|
| - "CREATE VIRTUAL TABLE temp.recover_favicons_bitmaps USING recover"
|
| - "("
|
| - "corrupt.favicon_bitmaps,"
|
| - "id ROWID,"
|
| - "icon_id INTEGER STRICT NOT NULL,"
|
| - "last_updated INTEGER,"
|
| - "image_data BLOB,"
|
| - "width INTEGER,"
|
| - "height INTEGER"
|
| - ")";
|
| - if (!recovery->db()->Execute(kRecoverySql)) {
|
| - // TODO(shess): Failure to create the recovery table probably
|
| - // means unrecoverable.
|
| - sql::Recovery::Rollback(recovery.Pass());
|
| - RecordRecoveryEvent(RECOVERY_EVENT_FAILED_RECOVER_FAVICON_BITMAPS);
|
| - return;
|
| - }
|
| -
|
| - const char kCopySql[] =
|
| - "INSERT OR REPLACE INTO main.favicon_bitmaps "
|
| - "SELECT id, icon_id, COALESCE(last_updated, 0), image_data, "
|
| - " COALESCE(width, 0), COALESCE(height, 0) "
|
| - "FROM recover_favicons_bitmaps";
|
| - if (!recovery->db()->Execute(kCopySql)) {
|
| - // TODO(shess): The recover_faviconbitmaps table should mask
|
| - // problems with the source file, so this implies failure to
|
| - // write to the recovery database.
|
| - sql::Recovery::Rollback(recovery.Pass());
|
| - RecordRecoveryEvent(RECOVERY_EVENT_FAILED_FAVICON_BITMAPS_INSERT);
|
| - return;
|
| - }
|
| - favicon_bitmaps_rows_recovered = recovery->db()->GetLastChangeCount();
|
| + if (!recovery->AutoRecoverTable("favicon_bitmaps", 0,
|
| + &favicon_bitmaps_rows_recovered)) {
|
| + sql::Recovery::Rollback(recovery.Pass());
|
| + RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS);
|
| + return;
|
| }
|
| -
|
| - // Setup icon_mapping table.
|
| - {
|
| - const char kRecoverySql[] =
|
| - "CREATE VIRTUAL TABLE temp.recover_icon_mapping USING recover"
|
| - "("
|
| - "corrupt.icon_mapping,"
|
| - "id ROWID,"
|
| - "page_url TEXT STRICT NOT NULL,"
|
| - "icon_id INTEGER STRICT"
|
| - ")";
|
| - if (!recovery->db()->Execute(kRecoverySql)) {
|
| - // TODO(shess): Failure to create the recovery table probably
|
| - // means unrecoverable.
|
| - sql::Recovery::Rollback(recovery.Pass());
|
| - RecordRecoveryEvent(RECOVERY_EVENT_FAILED_RECOVER_ICON_MAPPING);
|
| - return;
|
| - }
|
| -
|
| - const char kCopySql[] =
|
| - "INSERT OR REPLACE INTO main.icon_mapping "
|
| - "SELECT id, page_url, icon_id FROM recover_icon_mapping";
|
| - if (!recovery->db()->Execute(kCopySql)) {
|
| - // TODO(shess): The recover_icon_mapping table should mask
|
| - // problems with the source file, so this implies failure to
|
| - // write to the recovery database.
|
| - sql::Recovery::Rollback(recovery.Pass());
|
| - RecordRecoveryEvent(RECOVERY_EVENT_FAILED_ICON_MAPPING_INSERT);
|
| - return;
|
| - }
|
| - icon_mapping_rows_recovered = recovery->db()->GetLastChangeCount();
|
| + 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
|
|
|