Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(434)

Side by Side Diff: sql/recovery.cc

Issue 83323005: [sql] Annotate sql::Recovery failure cases. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Submitter is idiot - histograms.h. Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "sql/recovery.h" 5 #include "sql/recovery.h"
6 6
7 #include "base/files/file_path.h" 7 #include "base/files/file_path.h"
8 #include "base/format_macros.h" 8 #include "base/format_macros.h"
9 #include "base/logging.h" 9 #include "base/logging.h"
10 #include "base/metrics/histogram.h"
10 #include "base/metrics/sparse_histogram.h" 11 #include "base/metrics/sparse_histogram.h"
11 #include "base/strings/string_util.h" 12 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.h" 13 #include "base/strings/stringprintf.h"
13 #include "sql/connection.h" 14 #include "sql/connection.h"
14 #include "sql/statement.h" 15 #include "sql/statement.h"
15 #include "third_party/sqlite/sqlite3.h" 16 #include "third_party/sqlite/sqlite3.h"
16 17
17 namespace sql { 18 namespace sql {
18 19
20 namespace {
21
22 enum RecoveryEventType {
23 // Failed to open temporary database to recover into.
24 RECOVERY_FAILED_OPEN_TEMPORARY = 0,
25
26 // Failed to initialize recover vtable system.
27 RECOVERY_FAILED_VIRTUAL_TABLE_INIT,
28
29 // System SQLite doesn't support vtable.
30 RECOVERY_FAILED_VIRTUAL_TABLE_SYSTEM_SQLITE,
31
32 // Failed attempting to enable writable_schema.
33 RECOVERY_FAILED_WRITABLE_SCHEMA,
34
35 // Failed to attach the corrupt database to the temporary database.
36 RECOVERY_FAILED_ATTACH,
37
38 // Failed sqlite3_backup_init(). Error code in Sqlite.RecoveryHandle.
39 RECOVERY_FAILED_BACKUP_INIT,
40
41 // Failed sqlite3_backup_step(). Error code in Sqlite.RecoveryStep.
42 RECOVERY_FAILED_BACKUP_STEP,
43
44 // The target table contained a type which the code is not equipped
45 // to handle. This should only happen if things are fubar.
46 RECOVERY_FAILED_AUTORECOVER_UNRECOGNIZED_TYPE,
47
48 // The target table does not exist.
49 RECOVERY_FAILED_AUTORECOVER_MISSING_TABLE,
50
51 // The recovery virtual table creation failed.
52 RECOVERY_FAILED_AUTORECOVER_CREATE,
53
54 // Copying data from the recovery table to the target table failed.
55 RECOVERY_FAILED_AUTORECOVER_INSERT,
56
57 // Dropping the recovery virtual table failed.
58 RECOVERY_FAILED_AUTORECOVER_DROP,
59
60 // Failure creating recovery meta table.
61 RECOVERY_FAILED_META_CREATE,
62
63 // Failed in querying recovery meta table.
64 RECOVERY_FAILED_META_QUERY,
65
66 // No version key in recovery meta table.
67 RECOVERY_FAILED_META_NO_VERSION,
68
69 // Always keep this at the end.
70 RECOVERY_EVENT_MAX,
71 };
72
73 void RecordRecoveryEvent(RecoveryEventType recovery_event) {
74 UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryFailures",
75 recovery_event, RECOVERY_EVENT_MAX);
76 }
77
78 } // namespace
79
19 // static 80 // static
20 bool Recovery::FullRecoverySupported() { 81 bool Recovery::FullRecoverySupported() {
21 // TODO(shess): See comment in Init(). 82 // TODO(shess): See comment in Init().
22 #if defined(USE_SYSTEM_SQLITE) 83 #if defined(USE_SYSTEM_SQLITE)
23 return false; 84 return false;
24 #else 85 #else
25 return true; 86 return true;
26 #endif 87 #endif
27 } 88 }
28 89
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
95 // next database access, so immediately force an access. Enabling 156 // next database access, so immediately force an access. Enabling
96 // writable_schema allows processing through certain kinds of 157 // writable_schema allows processing through certain kinds of
97 // corruption. 158 // corruption.
98 // TODO(shess): It would be better to just close the handle, but it 159 // TODO(shess): It would be better to just close the handle, but it
99 // is necessary for the final backup which rewrites things. It 160 // is necessary for the final backup which rewrites things. It
100 // might be reasonable to close then re-open the handle. 161 // might be reasonable to close then re-open the handle.
101 ignore_result(db_->Execute("PRAGMA writable_schema=1")); 162 ignore_result(db_->Execute("PRAGMA writable_schema=1"));
102 ignore_result(db_->Execute("PRAGMA locking_mode=NORMAL")); 163 ignore_result(db_->Execute("PRAGMA locking_mode=NORMAL"));
103 ignore_result(db_->Execute("SELECT COUNT(*) FROM sqlite_master")); 164 ignore_result(db_->Execute("SELECT COUNT(*) FROM sqlite_master"));
104 165
105 if (!recover_db_.OpenTemporary()) 166 // TODO(shess): If this is a common failure case, it might be
167 // possible to fall back to a memory database. But it probably
168 // implies that the SQLite tmpdir logic is busted, which could cause
169 // a variety of other random issues in our code.
170 if (!recover_db_.OpenTemporary()) {
171 RecordRecoveryEvent(RECOVERY_FAILED_OPEN_TEMPORARY);
106 return false; 172 return false;
173 }
107 174
108 // TODO(shess): Figure out a story for USE_SYSTEM_SQLITE. The 175 // TODO(shess): Figure out a story for USE_SYSTEM_SQLITE. The
109 // virtual table implementation relies on SQLite internals for some 176 // virtual table implementation relies on SQLite internals for some
110 // types and functions, which could be copied inline to make it 177 // types and functions, which could be copied inline to make it
111 // standalone. Or an alternate implementation could try to read 178 // standalone. Or an alternate implementation could try to read
112 // through errors entirely at the SQLite level. 179 // through errors entirely at the SQLite level.
113 // 180 //
114 // For now, defer to the caller. The setup will succeed, but the 181 // For now, defer to the caller. The setup will succeed, but the
115 // later CREATE VIRTUAL TABLE call will fail, at which point the 182 // later CREATE VIRTUAL TABLE call will fail, at which point the
116 // caller can fire Unrecoverable(). 183 // caller can fire Unrecoverable().
117 #if !defined(USE_SYSTEM_SQLITE) 184 #if !defined(USE_SYSTEM_SQLITE)
118 int rc = recoverVtableInit(recover_db_.db_); 185 int rc = recoverVtableInit(recover_db_.db_);
119 if (rc != SQLITE_OK) { 186 if (rc != SQLITE_OK) {
187 RecordRecoveryEvent(RECOVERY_FAILED_VIRTUAL_TABLE_INIT);
120 LOG(ERROR) << "Failed to initialize recover module: " 188 LOG(ERROR) << "Failed to initialize recover module: "
121 << recover_db_.GetErrorMessage(); 189 << recover_db_.GetErrorMessage();
122 return false; 190 return false;
123 } 191 }
192 #else
193 // If this is infrequent enough, just wire it to Raze().
194 RecordRecoveryEvent(RECOVERY_FAILED_VIRTUAL_TABLE_SYSTEM_SQLITE);
124 #endif 195 #endif
125 196
126 // Turn on |SQLITE_RecoveryMode| for the handle, which allows 197 // Turn on |SQLITE_RecoveryMode| for the handle, which allows
127 // reading certain broken databases. 198 // reading certain broken databases.
128 if (!recover_db_.Execute("PRAGMA writable_schema=1")) 199 if (!recover_db_.Execute("PRAGMA writable_schema=1")) {
200 RecordRecoveryEvent(RECOVERY_FAILED_WRITABLE_SCHEMA);
129 return false; 201 return false;
202 }
130 203
131 if (!recover_db_.AttachDatabase(db_path, "corrupt")) 204 if (!recover_db_.AttachDatabase(db_path, "corrupt")) {
205 RecordRecoveryEvent(RECOVERY_FAILED_ATTACH);
132 return false; 206 return false;
207 }
133 208
134 return true; 209 return true;
135 } 210 }
136 211
137 bool Recovery::Backup() { 212 bool Recovery::Backup() {
138 CHECK(db_); 213 CHECK(db_);
139 CHECK(recover_db_.is_open()); 214 CHECK(recover_db_.is_open());
140 215
141 // TODO(shess): Some of the failure cases here may need further 216 // TODO(shess): Some of the failure cases here may need further
142 // exploration. Just as elsewhere, persistent problems probably 217 // exploration. Just as elsewhere, persistent problems probably
(...skipping 23 matching lines...) Expand all
166 // and attempt the backup again. 241 // and attempt the backup again.
167 // 242 //
168 // For now, this code attempts a best effort and records histograms 243 // For now, this code attempts a best effort and records histograms
169 // to inform future development. 244 // to inform future development.
170 245
171 // Backup the original db from the recovered db. 246 // Backup the original db from the recovered db.
172 const char* kMain = "main"; 247 const char* kMain = "main";
173 sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain, 248 sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain,
174 recover_db_.db_, kMain); 249 recover_db_.db_, kMain);
175 if (!backup) { 250 if (!backup) {
251 RecordRecoveryEvent(RECOVERY_FAILED_BACKUP_INIT);
252
176 // Error code is in the destination database handle. 253 // Error code is in the destination database handle.
177 int err = sqlite3_errcode(db_->db_); 254 int err = sqlite3_extended_errcode(db_->db_);
178 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err); 255 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err);
179 LOG(ERROR) << "sqlite3_backup_init() failed: " 256 LOG(ERROR) << "sqlite3_backup_init() failed: "
180 << sqlite3_errmsg(db_->db_); 257 << sqlite3_errmsg(db_->db_);
258
181 return false; 259 return false;
182 } 260 }
183 261
184 // -1 backs up the entire database. 262 // -1 backs up the entire database.
185 int rc = sqlite3_backup_step(backup, -1); 263 int rc = sqlite3_backup_step(backup, -1);
186 int pages = sqlite3_backup_pagecount(backup); 264 int pages = sqlite3_backup_pagecount(backup);
187 // TODO(shess): sqlite3_backup_finish() appears to allow returning a 265 // TODO(shess): sqlite3_backup_finish() appears to allow returning a
188 // different value from sqlite3_backup_step(). Circle back and 266 // different value from sqlite3_backup_step(). Circle back and
189 // figure out if that can usefully inform the decision of whether to 267 // figure out if that can usefully inform the decision of whether to
190 // retry or not. 268 // retry or not.
191 sqlite3_backup_finish(backup); 269 sqlite3_backup_finish(backup);
192 DCHECK_GT(pages, 0); 270 DCHECK_GT(pages, 0);
193 271
194 if (rc != SQLITE_DONE) { 272 if (rc != SQLITE_DONE) {
273 RecordRecoveryEvent(RECOVERY_FAILED_BACKUP_STEP);
195 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc); 274 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc);
196 LOG(ERROR) << "sqlite3_backup_step() failed: " 275 LOG(ERROR) << "sqlite3_backup_step() failed: "
197 << sqlite3_errmsg(db_->db_); 276 << sqlite3_errmsg(db_->db_);
198 } 277 }
199 278
200 // The destination database was locked. Give up, but leave the data 279 // The destination database was locked. Give up, but leave the data
201 // in place. Maybe it won't be locked next time. 280 // in place. Maybe it won't be locked next time.
202 if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) { 281 if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
203 Shutdown(POISON); 282 Shutdown(POISON);
204 return false; 283 return false;
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
299 column_decl += " BLOB"; 378 column_decl += " BLOB";
300 } else { 379 } else {
301 // TODO(shess): AFAICT, there remain: 380 // TODO(shess): AFAICT, there remain:
302 // - contains("CLOB") -> TEXT 381 // - contains("CLOB") -> TEXT
303 // - contains("REAL") -> REAL 382 // - contains("REAL") -> REAL
304 // - contains("FLOA") -> REAL 383 // - contains("FLOA") -> REAL
305 // - contains("DOUB") -> REAL 384 // - contains("DOUB") -> REAL
306 // - other -> "NUMERIC" 385 // - other -> "NUMERIC"
307 // Just code those in as they come up. 386 // Just code those in as they come up.
308 NOTREACHED() << " Unsupported type " << column_type; 387 NOTREACHED() << " Unsupported type " << column_type;
388 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_UNRECOGNIZED_TYPE);
309 return false; 389 return false;
310 } 390 }
311 391
312 // If column has constraint "NOT NULL", then inserting NULL into 392 // If column has constraint "NOT NULL", then inserting NULL into
313 // that column will fail. If the column has a non-NULL DEFAULT 393 // that column will fail. If the column has a non-NULL DEFAULT
314 // specified, the INSERT will handle it (see below). If the 394 // specified, the INSERT will handle it (see below). If the
315 // DEFAULT is also NULL, the row must be filtered out. 395 // DEFAULT is also NULL, the row must be filtered out.
316 // TODO(shess): The above scenario applies to INSERT OR REPLACE, 396 // TODO(shess): The above scenario applies to INSERT OR REPLACE,
317 // whereas INSERT OR IGNORE drops such rows. 397 // whereas INSERT OR IGNORE drops such rows.
318 // http://www.sqlite.org/lang_conflict.html 398 // http://www.sqlite.org/lang_conflict.html
(...skipping 10 matching lines...) Expand all
329 } else { 409 } else {
330 // The default value appears to be pre-quoted, as if it is 410 // The default value appears to be pre-quoted, as if it is
331 // literally from the sqlite_master CREATE statement. 411 // literally from the sqlite_master CREATE statement.
332 std::string default_value = s.ColumnString(4); 412 std::string default_value = s.ColumnString(4);
333 insert_columns.push_back(base::StringPrintf( 413 insert_columns.push_back(base::StringPrintf(
334 "IFNULL(%s,%s)", column_name.c_str(), default_value.c_str())); 414 "IFNULL(%s,%s)", column_name.c_str(), default_value.c_str()));
335 } 415 }
336 } 416 }
337 417
338 // Receiving no column information implies that the table doesn't exist. 418 // Receiving no column information implies that the table doesn't exist.
339 if (create_column_decls.empty()) 419 if (create_column_decls.empty()) {
420 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_MISSING_TABLE);
340 return false; 421 return false;
422 }
341 423
342 // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID. 424 // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID.
343 if (pk_column_count == 1 && !rowid_decl.empty()) 425 if (pk_column_count == 1 && !rowid_decl.empty())
344 create_column_decls[rowid_ofs] = rowid_decl; 426 create_column_decls[rowid_ofs] = rowid_decl;
345 427
346 // Additional columns accept anything. 428 // Additional columns accept anything.
347 // TODO(shess): ignoreN isn't well namespaced. But it will fail to 429 // TODO(shess): ignoreN isn't well namespaced. But it will fail to
348 // execute in case of conflicts. 430 // execute in case of conflicts.
349 for (size_t i = 0; i < extend_columns; ++i) { 431 for (size_t i = 0; i < extend_columns; ++i) {
350 create_column_decls.push_back( 432 create_column_decls.push_back(
351 base::StringPrintf("ignore%" PRIuS " ANY", i)); 433 base::StringPrintf("ignore%" PRIuS " ANY", i));
352 } 434 }
353 435
354 std::string recover_create(base::StringPrintf( 436 std::string recover_create(base::StringPrintf(
355 "CREATE VIRTUAL TABLE temp.recover_%s USING recover(corrupt.%s, %s)", 437 "CREATE VIRTUAL TABLE temp.recover_%s USING recover(corrupt.%s, %s)",
356 table_name, 438 table_name,
357 table_name, 439 table_name,
358 JoinString(create_column_decls, ',').c_str())); 440 JoinString(create_column_decls, ',').c_str()));
359 441
360 std::string recover_insert(base::StringPrintf( 442 std::string recover_insert(base::StringPrintf(
361 "INSERT OR REPLACE INTO main.%s SELECT %s FROM temp.recover_%s", 443 "INSERT OR REPLACE INTO main.%s SELECT %s FROM temp.recover_%s",
362 table_name, 444 table_name,
363 JoinString(insert_columns, ',').c_str(), 445 JoinString(insert_columns, ',').c_str(),
364 table_name)); 446 table_name));
365 447
366 std::string recover_drop(base::StringPrintf( 448 std::string recover_drop(base::StringPrintf(
367 "DROP TABLE temp.recover_%s", table_name)); 449 "DROP TABLE temp.recover_%s", table_name));
368 450
369 if (!db()->Execute(recover_create.c_str())) 451 if (!db()->Execute(recover_create.c_str())) {
452 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_CREATE);
370 return false; 453 return false;
454 }
371 455
372 if (!db()->Execute(recover_insert.c_str())) { 456 if (!db()->Execute(recover_insert.c_str())) {
457 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_INSERT);
373 ignore_result(db()->Execute(recover_drop.c_str())); 458 ignore_result(db()->Execute(recover_drop.c_str()));
374 return false; 459 return false;
375 } 460 }
376 461
377 *rows_recovered = db()->GetLastChangeCount(); 462 *rows_recovered = db()->GetLastChangeCount();
378 463
379 // TODO(shess): Is leaving the recover table around a breaker? 464 // TODO(shess): Is leaving the recover table around a breaker?
380 return db()->Execute(recover_drop.c_str()); 465 if (!db()->Execute(recover_drop.c_str())) {
466 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_DROP);
467 return false;
468 }
469 return true;
381 } 470 }
382 471
383 bool Recovery::SetupMeta() { 472 bool Recovery::SetupMeta() {
384 const char kCreateSql[] = 473 const char kCreateSql[] =
385 "CREATE VIRTUAL TABLE temp.recover_meta USING recover" 474 "CREATE VIRTUAL TABLE temp.recover_meta USING recover"
386 "(" 475 "("
387 "corrupt.meta," 476 "corrupt.meta,"
388 "key TEXT NOT NULL," 477 "key TEXT NOT NULL,"
389 "value ANY" // Whatever is stored. 478 "value ANY" // Whatever is stored.
390 ")"; 479 ")";
391 return db()->Execute(kCreateSql); 480 if (!db()->Execute(kCreateSql)) {
481 RecordRecoveryEvent(RECOVERY_FAILED_META_CREATE);
482 return false;
483 }
484 return true;
392 } 485 }
393 486
394 bool Recovery::GetMetaVersionNumber(int* version) { 487 bool Recovery::GetMetaVersionNumber(int* version) {
395 DCHECK(version); 488 DCHECK(version);
396 // TODO(shess): DCHECK(db()->DoesTableExist("temp.recover_meta")); 489 // TODO(shess): DCHECK(db()->DoesTableExist("temp.recover_meta"));
397 // Unfortunately, DoesTableExist() queries sqlite_master, not 490 // Unfortunately, DoesTableExist() queries sqlite_master, not
398 // sqlite_temp_master. 491 // sqlite_temp_master.
399 492
400 const char kVersionSql[] = 493 const char kVersionSql[] =
401 "SELECT value FROM temp.recover_meta WHERE key = 'version'"; 494 "SELECT value FROM temp.recover_meta WHERE key = 'version'";
402 sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql)); 495 sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql));
403 if (!recovery_version.Step()) 496 if (!recovery_version.Step()) {
497 if (!recovery_version.Succeeded()) {
498 RecordRecoveryEvent(RECOVERY_FAILED_META_QUERY);
499 } else {
500 RecordRecoveryEvent(RECOVERY_FAILED_META_NO_VERSION);
501 }
404 return false; 502 return false;
503 }
405 504
406 *version = recovery_version.ColumnInt(0); 505 *version = recovery_version.ColumnInt(0);
407 return true; 506 return true;
408 } 507 }
409 508
410 } // namespace sql 509 } // namespace sql
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698