| Index: sql/recovery.cc
|
| diff --git a/sql/recovery.cc b/sql/recovery.cc
|
| index 46f609b8d9386d537ec35e0c1ce2f093cc808158..cd53572e85e49c717567f5197f8d584273d101e2 100644
|
| --- a/sql/recovery.cc
|
| +++ b/sql/recovery.cc
|
| @@ -7,6 +7,7 @@
|
| #include "base/files/file_path.h"
|
| #include "base/format_macros.h"
|
| #include "base/logging.h"
|
| +#include "base/metrics/histogram.h"
|
| #include "base/metrics/sparse_histogram.h"
|
| #include "base/strings/string_util.h"
|
| #include "base/strings/stringprintf.h"
|
| @@ -16,6 +17,66 @@
|
|
|
| namespace sql {
|
|
|
| +namespace {
|
| +
|
| +enum RecoveryEventType {
|
| + // Failed to open temporary database to recover into.
|
| + RECOVERY_FAILED_OPEN_TEMPORARY = 0,
|
| +
|
| + // Failed to initialize recover vtable system.
|
| + RECOVERY_FAILED_VIRTUAL_TABLE_INIT,
|
| +
|
| + // System SQLite doesn't support vtable.
|
| + RECOVERY_FAILED_VIRTUAL_TABLE_SYSTEM_SQLITE,
|
| +
|
| + // Failed attempting to enable writable_schema.
|
| + RECOVERY_FAILED_WRITABLE_SCHEMA,
|
| +
|
| + // Failed to attach the corrupt database to the temporary database.
|
| + RECOVERY_FAILED_ATTACH,
|
| +
|
| + // Failed sqlite3_backup_init(). Error code in Sqlite.RecoveryHandle.
|
| + RECOVERY_FAILED_BACKUP_INIT,
|
| +
|
| + // Failed sqlite3_backup_step(). Error code in Sqlite.RecoveryStep.
|
| + RECOVERY_FAILED_BACKUP_STEP,
|
| +
|
| + // The target table contained a type which the code is not equipped
|
| + // to handle. This should only happen if things are fubar.
|
| + RECOVERY_FAILED_AUTORECOVER_UNRECOGNIZED_TYPE,
|
| +
|
| + // The target table does not exist.
|
| + RECOVERY_FAILED_AUTORECOVER_MISSING_TABLE,
|
| +
|
| + // The recovery virtual table creation failed.
|
| + RECOVERY_FAILED_AUTORECOVER_CREATE,
|
| +
|
| + // Copying data from the recovery table to the target table failed.
|
| + RECOVERY_FAILED_AUTORECOVER_INSERT,
|
| +
|
| + // Dropping the recovery virtual table failed.
|
| + RECOVERY_FAILED_AUTORECOVER_DROP,
|
| +
|
| + // Failure creating recovery meta table.
|
| + RECOVERY_FAILED_META_CREATE,
|
| +
|
| + // Failed in querying recovery meta table.
|
| + RECOVERY_FAILED_META_QUERY,
|
| +
|
| + // No version key in recovery meta table.
|
| + RECOVERY_FAILED_META_NO_VERSION,
|
| +
|
| + // Always keep this at the end.
|
| + RECOVERY_EVENT_MAX,
|
| +};
|
| +
|
| +void RecordRecoveryEvent(RecoveryEventType recovery_event) {
|
| + UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryFailures",
|
| + recovery_event, RECOVERY_EVENT_MAX);
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| // static
|
| bool Recovery::FullRecoverySupported() {
|
| // TODO(shess): See comment in Init().
|
| @@ -102,8 +163,14 @@ bool Recovery::Init(const base::FilePath& db_path) {
|
| ignore_result(db_->Execute("PRAGMA locking_mode=NORMAL"));
|
| ignore_result(db_->Execute("SELECT COUNT(*) FROM sqlite_master"));
|
|
|
| - if (!recover_db_.OpenTemporary())
|
| + // TODO(shess): If this is a common failure case, it might be
|
| + // possible to fall back to a memory database. But it probably
|
| + // implies that the SQLite tmpdir logic is busted, which could cause
|
| + // a variety of other random issues in our code.
|
| + if (!recover_db_.OpenTemporary()) {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_OPEN_TEMPORARY);
|
| return false;
|
| + }
|
|
|
| // TODO(shess): Figure out a story for USE_SYSTEM_SQLITE. The
|
| // virtual table implementation relies on SQLite internals for some
|
| @@ -117,19 +184,27 @@ bool Recovery::Init(const base::FilePath& db_path) {
|
| #if !defined(USE_SYSTEM_SQLITE)
|
| int rc = recoverVtableInit(recover_db_.db_);
|
| if (rc != SQLITE_OK) {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_VIRTUAL_TABLE_INIT);
|
| LOG(ERROR) << "Failed to initialize recover module: "
|
| << recover_db_.GetErrorMessage();
|
| return false;
|
| }
|
| +#else
|
| + // If this is infrequent enough, just wire it to Raze().
|
| + RecordRecoveryEvent(RECOVERY_FAILED_VIRTUAL_TABLE_SYSTEM_SQLITE);
|
| #endif
|
|
|
| // Turn on |SQLITE_RecoveryMode| for the handle, which allows
|
| // reading certain broken databases.
|
| - if (!recover_db_.Execute("PRAGMA writable_schema=1"))
|
| + if (!recover_db_.Execute("PRAGMA writable_schema=1")) {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_WRITABLE_SCHEMA);
|
| return false;
|
| + }
|
|
|
| - if (!recover_db_.AttachDatabase(db_path, "corrupt"))
|
| + if (!recover_db_.AttachDatabase(db_path, "corrupt")) {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_ATTACH);
|
| return false;
|
| + }
|
|
|
| return true;
|
| }
|
| @@ -173,11 +248,14 @@ bool Recovery::Backup() {
|
| sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain,
|
| recover_db_.db_, kMain);
|
| if (!backup) {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_BACKUP_INIT);
|
| +
|
| // Error code is in the destination database handle.
|
| - int err = sqlite3_errcode(db_->db_);
|
| + int err = sqlite3_extended_errcode(db_->db_);
|
| UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err);
|
| LOG(ERROR) << "sqlite3_backup_init() failed: "
|
| << sqlite3_errmsg(db_->db_);
|
| +
|
| return false;
|
| }
|
|
|
| @@ -192,6 +270,7 @@ bool Recovery::Backup() {
|
| DCHECK_GT(pages, 0);
|
|
|
| if (rc != SQLITE_DONE) {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_BACKUP_STEP);
|
| UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc);
|
| LOG(ERROR) << "sqlite3_backup_step() failed: "
|
| << sqlite3_errmsg(db_->db_);
|
| @@ -306,6 +385,7 @@ bool Recovery::AutoRecoverTable(const char* table_name,
|
| // - other -> "NUMERIC"
|
| // Just code those in as they come up.
|
| NOTREACHED() << " Unsupported type " << column_type;
|
| + RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_UNRECOGNIZED_TYPE);
|
| return false;
|
| }
|
|
|
| @@ -336,8 +416,10 @@ bool Recovery::AutoRecoverTable(const char* table_name,
|
| }
|
|
|
| // Receiving no column information implies that the table doesn't exist.
|
| - if (create_column_decls.empty())
|
| + if (create_column_decls.empty()) {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_MISSING_TABLE);
|
| return false;
|
| + }
|
|
|
| // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID.
|
| if (pk_column_count == 1 && !rowid_decl.empty())
|
| @@ -366,10 +448,13 @@ bool Recovery::AutoRecoverTable(const char* table_name,
|
| std::string recover_drop(base::StringPrintf(
|
| "DROP TABLE temp.recover_%s", table_name));
|
|
|
| - if (!db()->Execute(recover_create.c_str()))
|
| + if (!db()->Execute(recover_create.c_str())) {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_CREATE);
|
| return false;
|
| + }
|
|
|
| if (!db()->Execute(recover_insert.c_str())) {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_INSERT);
|
| ignore_result(db()->Execute(recover_drop.c_str()));
|
| return false;
|
| }
|
| @@ -377,7 +462,11 @@ bool Recovery::AutoRecoverTable(const char* table_name,
|
| *rows_recovered = db()->GetLastChangeCount();
|
|
|
| // TODO(shess): Is leaving the recover table around a breaker?
|
| - return db()->Execute(recover_drop.c_str());
|
| + if (!db()->Execute(recover_drop.c_str())) {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_DROP);
|
| + return false;
|
| + }
|
| + return true;
|
| }
|
|
|
| bool Recovery::SetupMeta() {
|
| @@ -388,7 +477,11 @@ bool Recovery::SetupMeta() {
|
| "key TEXT NOT NULL,"
|
| "value ANY" // Whatever is stored.
|
| ")";
|
| - return db()->Execute(kCreateSql);
|
| + if (!db()->Execute(kCreateSql)) {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_META_CREATE);
|
| + return false;
|
| + }
|
| + return true;
|
| }
|
|
|
| bool Recovery::GetMetaVersionNumber(int* version) {
|
| @@ -400,8 +493,14 @@ bool Recovery::GetMetaVersionNumber(int* version) {
|
| const char kVersionSql[] =
|
| "SELECT value FROM temp.recover_meta WHERE key = 'version'";
|
| sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql));
|
| - if (!recovery_version.Step())
|
| + if (!recovery_version.Step()) {
|
| + if (!recovery_version.Succeeded()) {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_META_QUERY);
|
| + } else {
|
| + RecordRecoveryEvent(RECOVERY_FAILED_META_NO_VERSION);
|
| + }
|
| return false;
|
| + }
|
|
|
| *version = recovery_version.ColumnInt(0);
|
| return true;
|
|
|