Index: chrome/browser/history/thumbnail_database.cc |
diff --git a/chrome/browser/history/thumbnail_database.cc b/chrome/browser/history/thumbnail_database.cc |
index fe6e34f29bd212be85d209a98ab0b65842112e30..080db559dc9052bd0c8aaa3c1374001c4c79a6b2 100644 |
--- a/chrome/browser/history/thumbnail_database.cc |
+++ b/chrome/browser/history/thumbnail_database.cc |
@@ -275,6 +275,60 @@ void GenerateDiagnostics(sql::Connection* db, |
} |
} |
+// 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 @@ bool InitIndices(sql::Connection* db) { |
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,75 +468,104 @@ void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) { |
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); |
- |
- // 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; |
- } |
+ // 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; |
} |
- // 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("favicons", 0, &favicons_rows_recovered)) { |
sql::Recovery::Rollback(recovery.Pass()); |
- RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT); |
+ 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 && |
+ 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 |
@@ -493,114 +585,24 @@ void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) { |
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 |