OLD | NEW |
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 // Init() completed successfully. |
| 24 RECOVERY_SUCCESS_INIT = 0, |
| 25 |
| 26 // Failed to open temporary database to recover into. |
| 27 RECOVERY_FAILED_OPEN_TEMPORARY, |
| 28 |
| 29 // Failed to initialize recover vtable system. |
| 30 RECOVERY_FAILED_VIRTUAL_TABLE_INIT, |
| 31 |
| 32 // System SQLite doesn't support vtable. |
| 33 RECOVERY_FAILED_VIRTUAL_TABLE_SYSTEM_SQLITE, |
| 34 |
| 35 // Failed attempting to enable writable_schema. |
| 36 RECOVERY_FAILED_WRITABLE_SCHEMA, |
| 37 |
| 38 // Failed to attach the corrupt database to the temporary database. |
| 39 RECOVERY_FAILED_ATTACH, |
| 40 |
| 41 // Backup() successfully completed. |
| 42 RECOVERY_SUCCESS_BACKUP, |
| 43 |
| 44 // Failed sqlite3_backup_init(). Error code in Sqlite.RecoveryHandle. |
| 45 RECOVERY_FAILED_BACKUP_INIT, |
| 46 |
| 47 // Failed sqlite3_backup_step(). Error code in Sqlite.RecoveryStep. |
| 48 RECOVERY_FAILED_BACKUP_STEP, |
| 49 |
| 50 // AutoRecoverTable() successfully completed. |
| 51 RECOVERY_SUCCESS_AUTORECOVER, |
| 52 |
| 53 // The target table contained a type which the code is not equipped |
| 54 // to handle. This should only happen if things are fubar. |
| 55 RECOVERY_FAILED_AUTORECOVER_UNRECOGNIZED_TYPE, |
| 56 |
| 57 // The target table does not exist. |
| 58 RECOVERY_FAILED_AUTORECOVER_MISSING_TABLE, |
| 59 |
| 60 // The recovery virtual table creation failed. |
| 61 RECOVERY_FAILED_AUTORECOVER_CREATE, |
| 62 |
| 63 // Copying data from the recovery table to the target table failed. |
| 64 RECOVERY_FAILED_AUTORECOVER_INSERT, |
| 65 |
| 66 // Dropping the recovery virtual table failed. |
| 67 RECOVERY_FAILED_AUTORECOVER_DROP, |
| 68 |
| 69 // SetupMeta() successfully completed. |
| 70 RECOVERY_SUCCESS_SETUP_META, |
| 71 |
| 72 // Failure creating recovery meta table. |
| 73 RECOVERY_FAILED_META_CREATE, |
| 74 |
| 75 // GetMetaVersionNumber() successfully completed. |
| 76 RECOVERY_SUCCESS_META_VERSION, |
| 77 |
| 78 // Failed in querying recovery meta table. |
| 79 RECOVERY_FAILED_META_QUERY, |
| 80 |
| 81 // No version key in recovery meta table. |
| 82 RECOVERY_FAILED_META_NO_VERSION, |
| 83 |
| 84 // Always keep this at the end. |
| 85 RECOVERY_EVENT_MAX, |
| 86 }; |
| 87 |
| 88 void RecordRecoveryEvent(RecoveryEventType recovery_event) { |
| 89 UMA_HISTOGRAM_ENUMERATION("Sqlite.RecoveryEvents", |
| 90 recovery_event, RECOVERY_EVENT_MAX); |
| 91 } |
| 92 |
| 93 } // namespace |
| 94 |
19 // static | 95 // static |
20 bool Recovery::FullRecoverySupported() { | 96 bool Recovery::FullRecoverySupported() { |
21 // TODO(shess): See comment in Init(). | 97 // TODO(shess): See comment in Init(). |
22 #if defined(USE_SYSTEM_SQLITE) | 98 #if defined(USE_SYSTEM_SQLITE) |
23 return false; | 99 return false; |
24 #else | 100 #else |
25 return true; | 101 return true; |
26 #endif | 102 #endif |
27 } | 103 } |
28 | 104 |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
95 // next database access, so immediately force an access. Enabling | 171 // next database access, so immediately force an access. Enabling |
96 // writable_schema allows processing through certain kinds of | 172 // writable_schema allows processing through certain kinds of |
97 // corruption. | 173 // corruption. |
98 // TODO(shess): It would be better to just close the handle, but it | 174 // TODO(shess): It would be better to just close the handle, but it |
99 // is necessary for the final backup which rewrites things. It | 175 // is necessary for the final backup which rewrites things. It |
100 // might be reasonable to close then re-open the handle. | 176 // might be reasonable to close then re-open the handle. |
101 ignore_result(db_->Execute("PRAGMA writable_schema=1")); | 177 ignore_result(db_->Execute("PRAGMA writable_schema=1")); |
102 ignore_result(db_->Execute("PRAGMA locking_mode=NORMAL")); | 178 ignore_result(db_->Execute("PRAGMA locking_mode=NORMAL")); |
103 ignore_result(db_->Execute("SELECT COUNT(*) FROM sqlite_master")); | 179 ignore_result(db_->Execute("SELECT COUNT(*) FROM sqlite_master")); |
104 | 180 |
105 if (!recover_db_.OpenTemporary()) | 181 // TODO(shess): If this is a common failure case, it might be |
| 182 // possible to fall back to a memory database. But it probably |
| 183 // implies that the SQLite tmpdir logic is busted, which could cause |
| 184 // a variety of other random issues in our code. |
| 185 if (!recover_db_.OpenTemporary()) { |
| 186 RecordRecoveryEvent(RECOVERY_FAILED_OPEN_TEMPORARY); |
106 return false; | 187 return false; |
| 188 } |
107 | 189 |
108 // TODO(shess): Figure out a story for USE_SYSTEM_SQLITE. The | 190 // TODO(shess): Figure out a story for USE_SYSTEM_SQLITE. The |
109 // virtual table implementation relies on SQLite internals for some | 191 // virtual table implementation relies on SQLite internals for some |
110 // types and functions, which could be copied inline to make it | 192 // types and functions, which could be copied inline to make it |
111 // standalone. Or an alternate implementation could try to read | 193 // standalone. Or an alternate implementation could try to read |
112 // through errors entirely at the SQLite level. | 194 // through errors entirely at the SQLite level. |
113 // | 195 // |
114 // For now, defer to the caller. The setup will succeed, but the | 196 // For now, defer to the caller. The setup will succeed, but the |
115 // later CREATE VIRTUAL TABLE call will fail, at which point the | 197 // later CREATE VIRTUAL TABLE call will fail, at which point the |
116 // caller can fire Unrecoverable(). | 198 // caller can fire Unrecoverable(). |
117 #if !defined(USE_SYSTEM_SQLITE) | 199 #if !defined(USE_SYSTEM_SQLITE) |
118 int rc = recoverVtableInit(recover_db_.db_); | 200 int rc = recoverVtableInit(recover_db_.db_); |
119 if (rc != SQLITE_OK) { | 201 if (rc != SQLITE_OK) { |
| 202 RecordRecoveryEvent(RECOVERY_FAILED_VIRTUAL_TABLE_INIT); |
120 LOG(ERROR) << "Failed to initialize recover module: " | 203 LOG(ERROR) << "Failed to initialize recover module: " |
121 << recover_db_.GetErrorMessage(); | 204 << recover_db_.GetErrorMessage(); |
122 return false; | 205 return false; |
123 } | 206 } |
| 207 #else |
| 208 // If this is infrequent enough, just wire it to Raze(). |
| 209 RecordRecoveryEvent(RECOVERY_FAILED_VIRTUAL_TABLE_SYSTEM_SQLITE); |
124 #endif | 210 #endif |
125 | 211 |
126 // Turn on |SQLITE_RecoveryMode| for the handle, which allows | 212 // Turn on |SQLITE_RecoveryMode| for the handle, which allows |
127 // reading certain broken databases. | 213 // reading certain broken databases. |
128 if (!recover_db_.Execute("PRAGMA writable_schema=1")) | 214 if (!recover_db_.Execute("PRAGMA writable_schema=1")) { |
| 215 RecordRecoveryEvent(RECOVERY_FAILED_WRITABLE_SCHEMA); |
129 return false; | 216 return false; |
| 217 } |
130 | 218 |
131 if (!recover_db_.AttachDatabase(db_path, "corrupt")) | 219 if (!recover_db_.AttachDatabase(db_path, "corrupt")) { |
| 220 RecordRecoveryEvent(RECOVERY_FAILED_ATTACH); |
132 return false; | 221 return false; |
| 222 } |
133 | 223 |
| 224 RecordRecoveryEvent(RECOVERY_SUCCESS_INIT); |
134 return true; | 225 return true; |
135 } | 226 } |
136 | 227 |
137 bool Recovery::Backup() { | 228 bool Recovery::Backup() { |
138 CHECK(db_); | 229 CHECK(db_); |
139 CHECK(recover_db_.is_open()); | 230 CHECK(recover_db_.is_open()); |
140 | 231 |
141 // TODO(shess): Some of the failure cases here may need further | 232 // TODO(shess): Some of the failure cases here may need further |
142 // exploration. Just as elsewhere, persistent problems probably | 233 // exploration. Just as elsewhere, persistent problems probably |
143 // need to be razed, while anything which might succeed on a future | 234 // need to be razed, while anything which might succeed on a future |
(...skipping 22 matching lines...) Expand all Loading... |
166 // and attempt the backup again. | 257 // and attempt the backup again. |
167 // | 258 // |
168 // For now, this code attempts a best effort and records histograms | 259 // For now, this code attempts a best effort and records histograms |
169 // to inform future development. | 260 // to inform future development. |
170 | 261 |
171 // Backup the original db from the recovered db. | 262 // Backup the original db from the recovered db. |
172 const char* kMain = "main"; | 263 const char* kMain = "main"; |
173 sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain, | 264 sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain, |
174 recover_db_.db_, kMain); | 265 recover_db_.db_, kMain); |
175 if (!backup) { | 266 if (!backup) { |
| 267 RecordRecoveryEvent(RECOVERY_FAILED_BACKUP_INIT); |
| 268 |
176 // Error code is in the destination database handle. | 269 // Error code is in the destination database handle. |
177 int err = sqlite3_errcode(db_->db_); | 270 int err = sqlite3_extended_errcode(db_->db_); |
178 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err); | 271 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err); |
179 LOG(ERROR) << "sqlite3_backup_init() failed: " | 272 LOG(ERROR) << "sqlite3_backup_init() failed: " |
180 << sqlite3_errmsg(db_->db_); | 273 << sqlite3_errmsg(db_->db_); |
| 274 |
181 return false; | 275 return false; |
182 } | 276 } |
183 | 277 |
184 // -1 backs up the entire database. | 278 // -1 backs up the entire database. |
185 int rc = sqlite3_backup_step(backup, -1); | 279 int rc = sqlite3_backup_step(backup, -1); |
186 int pages = sqlite3_backup_pagecount(backup); | 280 int pages = sqlite3_backup_pagecount(backup); |
187 // TODO(shess): sqlite3_backup_finish() appears to allow returning a | 281 // TODO(shess): sqlite3_backup_finish() appears to allow returning a |
188 // different value from sqlite3_backup_step(). Circle back and | 282 // different value from sqlite3_backup_step(). Circle back and |
189 // figure out if that can usefully inform the decision of whether to | 283 // figure out if that can usefully inform the decision of whether to |
190 // retry or not. | 284 // retry or not. |
191 sqlite3_backup_finish(backup); | 285 sqlite3_backup_finish(backup); |
192 DCHECK_GT(pages, 0); | 286 DCHECK_GT(pages, 0); |
193 | 287 |
194 if (rc != SQLITE_DONE) { | 288 if (rc != SQLITE_DONE) { |
| 289 RecordRecoveryEvent(RECOVERY_FAILED_BACKUP_STEP); |
195 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc); | 290 UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc); |
196 LOG(ERROR) << "sqlite3_backup_step() failed: " | 291 LOG(ERROR) << "sqlite3_backup_step() failed: " |
197 << sqlite3_errmsg(db_->db_); | 292 << sqlite3_errmsg(db_->db_); |
198 } | 293 } |
199 | 294 |
200 // The destination database was locked. Give up, but leave the data | 295 // The destination database was locked. Give up, but leave the data |
201 // in place. Maybe it won't be locked next time. | 296 // in place. Maybe it won't be locked next time. |
202 if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) { | 297 if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) { |
203 Shutdown(POISON); | 298 Shutdown(POISON); |
204 return false; | 299 return false; |
205 } | 300 } |
206 | 301 |
207 // Running out of memory should be transient, retry later. | 302 // Running out of memory should be transient, retry later. |
208 if (rc == SQLITE_NOMEM) { | 303 if (rc == SQLITE_NOMEM) { |
209 Shutdown(POISON); | 304 Shutdown(POISON); |
210 return false; | 305 return false; |
211 } | 306 } |
212 | 307 |
213 // TODO(shess): For now, leave the original database alone, pending | 308 // TODO(shess): For now, leave the original database alone, pending |
214 // results from Sqlite.RecoveryStep. Some errors should probably | 309 // results from Sqlite.RecoveryStep. Some errors should probably |
215 // route to RAZE_AND_POISON. | 310 // route to RAZE_AND_POISON. |
216 if (rc != SQLITE_DONE) { | 311 if (rc != SQLITE_DONE) { |
217 Shutdown(POISON); | 312 Shutdown(POISON); |
218 return false; | 313 return false; |
219 } | 314 } |
220 | 315 |
221 // Clean up the recovery db, and terminate the main database | 316 // Clean up the recovery db, and terminate the main database |
222 // connection. | 317 // connection. |
| 318 RecordRecoveryEvent(RECOVERY_SUCCESS_BACKUP); |
223 Shutdown(POISON); | 319 Shutdown(POISON); |
224 return true; | 320 return true; |
225 } | 321 } |
226 | 322 |
227 void Recovery::Shutdown(Recovery::Disposition raze) { | 323 void Recovery::Shutdown(Recovery::Disposition raze) { |
228 if (!db_) | 324 if (!db_) |
229 return; | 325 return; |
230 | 326 |
231 recover_db_.Close(); | 327 recover_db_.Close(); |
232 if (raze == RAZE_AND_POISON) { | 328 if (raze == RAZE_AND_POISON) { |
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
299 column_decl += " BLOB"; | 395 column_decl += " BLOB"; |
300 } else { | 396 } else { |
301 // TODO(shess): AFAICT, there remain: | 397 // TODO(shess): AFAICT, there remain: |
302 // - contains("CLOB") -> TEXT | 398 // - contains("CLOB") -> TEXT |
303 // - contains("REAL") -> REAL | 399 // - contains("REAL") -> REAL |
304 // - contains("FLOA") -> REAL | 400 // - contains("FLOA") -> REAL |
305 // - contains("DOUB") -> REAL | 401 // - contains("DOUB") -> REAL |
306 // - other -> "NUMERIC" | 402 // - other -> "NUMERIC" |
307 // Just code those in as they come up. | 403 // Just code those in as they come up. |
308 NOTREACHED() << " Unsupported type " << column_type; | 404 NOTREACHED() << " Unsupported type " << column_type; |
| 405 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_UNRECOGNIZED_TYPE); |
309 return false; | 406 return false; |
310 } | 407 } |
311 | 408 |
312 // If column has constraint "NOT NULL", then inserting NULL into | 409 // If column has constraint "NOT NULL", then inserting NULL into |
313 // that column will fail. If the column has a non-NULL DEFAULT | 410 // that column will fail. If the column has a non-NULL DEFAULT |
314 // specified, the INSERT will handle it (see below). If the | 411 // specified, the INSERT will handle it (see below). If the |
315 // DEFAULT is also NULL, the row must be filtered out. | 412 // DEFAULT is also NULL, the row must be filtered out. |
316 // TODO(shess): The above scenario applies to INSERT OR REPLACE, | 413 // TODO(shess): The above scenario applies to INSERT OR REPLACE, |
317 // whereas INSERT OR IGNORE drops such rows. | 414 // whereas INSERT OR IGNORE drops such rows. |
318 // http://www.sqlite.org/lang_conflict.html | 415 // http://www.sqlite.org/lang_conflict.html |
(...skipping 10 matching lines...) Expand all Loading... |
329 } else { | 426 } else { |
330 // The default value appears to be pre-quoted, as if it is | 427 // The default value appears to be pre-quoted, as if it is |
331 // literally from the sqlite_master CREATE statement. | 428 // literally from the sqlite_master CREATE statement. |
332 std::string default_value = s.ColumnString(4); | 429 std::string default_value = s.ColumnString(4); |
333 insert_columns.push_back(base::StringPrintf( | 430 insert_columns.push_back(base::StringPrintf( |
334 "IFNULL(%s,%s)", column_name.c_str(), default_value.c_str())); | 431 "IFNULL(%s,%s)", column_name.c_str(), default_value.c_str())); |
335 } | 432 } |
336 } | 433 } |
337 | 434 |
338 // Receiving no column information implies that the table doesn't exist. | 435 // Receiving no column information implies that the table doesn't exist. |
339 if (create_column_decls.empty()) | 436 if (create_column_decls.empty()) { |
| 437 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_MISSING_TABLE); |
340 return false; | 438 return false; |
| 439 } |
341 | 440 |
342 // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID. | 441 // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID. |
343 if (pk_column_count == 1 && !rowid_decl.empty()) | 442 if (pk_column_count == 1 && !rowid_decl.empty()) |
344 create_column_decls[rowid_ofs] = rowid_decl; | 443 create_column_decls[rowid_ofs] = rowid_decl; |
345 | 444 |
346 // Additional columns accept anything. | 445 // Additional columns accept anything. |
347 // TODO(shess): ignoreN isn't well namespaced. But it will fail to | 446 // TODO(shess): ignoreN isn't well namespaced. But it will fail to |
348 // execute in case of conflicts. | 447 // execute in case of conflicts. |
349 for (size_t i = 0; i < extend_columns; ++i) { | 448 for (size_t i = 0; i < extend_columns; ++i) { |
350 create_column_decls.push_back( | 449 create_column_decls.push_back( |
351 base::StringPrintf("ignore%" PRIuS " ANY", i)); | 450 base::StringPrintf("ignore%" PRIuS " ANY", i)); |
352 } | 451 } |
353 | 452 |
354 std::string recover_create(base::StringPrintf( | 453 std::string recover_create(base::StringPrintf( |
355 "CREATE VIRTUAL TABLE temp.recover_%s USING recover(corrupt.%s, %s)", | 454 "CREATE VIRTUAL TABLE temp.recover_%s USING recover(corrupt.%s, %s)", |
356 table_name, | 455 table_name, |
357 table_name, | 456 table_name, |
358 JoinString(create_column_decls, ',').c_str())); | 457 JoinString(create_column_decls, ',').c_str())); |
359 | 458 |
360 std::string recover_insert(base::StringPrintf( | 459 std::string recover_insert(base::StringPrintf( |
361 "INSERT OR REPLACE INTO main.%s SELECT %s FROM temp.recover_%s", | 460 "INSERT OR REPLACE INTO main.%s SELECT %s FROM temp.recover_%s", |
362 table_name, | 461 table_name, |
363 JoinString(insert_columns, ',').c_str(), | 462 JoinString(insert_columns, ',').c_str(), |
364 table_name)); | 463 table_name)); |
365 | 464 |
366 std::string recover_drop(base::StringPrintf( | 465 std::string recover_drop(base::StringPrintf( |
367 "DROP TABLE temp.recover_%s", table_name)); | 466 "DROP TABLE temp.recover_%s", table_name)); |
368 | 467 |
369 if (!db()->Execute(recover_create.c_str())) | 468 if (!db()->Execute(recover_create.c_str())) { |
| 469 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_CREATE); |
370 return false; | 470 return false; |
| 471 } |
371 | 472 |
372 if (!db()->Execute(recover_insert.c_str())) { | 473 if (!db()->Execute(recover_insert.c_str())) { |
| 474 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_INSERT); |
373 ignore_result(db()->Execute(recover_drop.c_str())); | 475 ignore_result(db()->Execute(recover_drop.c_str())); |
374 return false; | 476 return false; |
375 } | 477 } |
376 | 478 |
377 *rows_recovered = db()->GetLastChangeCount(); | 479 *rows_recovered = db()->GetLastChangeCount(); |
378 | 480 |
379 // TODO(shess): Is leaving the recover table around a breaker? | 481 // TODO(shess): Is leaving the recover table around a breaker? |
380 return db()->Execute(recover_drop.c_str()); | 482 if (!db()->Execute(recover_drop.c_str())) { |
| 483 RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_DROP); |
| 484 return false; |
| 485 } |
| 486 RecordRecoveryEvent(RECOVERY_SUCCESS_AUTORECOVER); |
| 487 return true; |
381 } | 488 } |
382 | 489 |
383 bool Recovery::SetupMeta() { | 490 bool Recovery::SetupMeta() { |
384 const char kCreateSql[] = | 491 const char kCreateSql[] = |
385 "CREATE VIRTUAL TABLE temp.recover_meta USING recover" | 492 "CREATE VIRTUAL TABLE temp.recover_meta USING recover" |
386 "(" | 493 "(" |
387 "corrupt.meta," | 494 "corrupt.meta," |
388 "key TEXT NOT NULL," | 495 "key TEXT NOT NULL," |
389 "value ANY" // Whatever is stored. | 496 "value ANY" // Whatever is stored. |
390 ")"; | 497 ")"; |
391 return db()->Execute(kCreateSql); | 498 if (!db()->Execute(kCreateSql)) { |
| 499 RecordRecoveryEvent(RECOVERY_FAILED_META_CREATE); |
| 500 return false; |
| 501 } |
| 502 RecordRecoveryEvent(RECOVERY_SUCCESS_SETUP_META); |
| 503 return true; |
392 } | 504 } |
393 | 505 |
394 bool Recovery::GetMetaVersionNumber(int* version) { | 506 bool Recovery::GetMetaVersionNumber(int* version) { |
395 DCHECK(version); | 507 DCHECK(version); |
396 // TODO(shess): DCHECK(db()->DoesTableExist("temp.recover_meta")); | 508 // TODO(shess): DCHECK(db()->DoesTableExist("temp.recover_meta")); |
397 // Unfortunately, DoesTableExist() queries sqlite_master, not | 509 // Unfortunately, DoesTableExist() queries sqlite_master, not |
398 // sqlite_temp_master. | 510 // sqlite_temp_master. |
399 | 511 |
400 const char kVersionSql[] = | 512 const char kVersionSql[] = |
401 "SELECT value FROM temp.recover_meta WHERE key = 'version'"; | 513 "SELECT value FROM temp.recover_meta WHERE key = 'version'"; |
402 sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql)); | 514 sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql)); |
403 if (!recovery_version.Step()) | 515 if (!recovery_version.Step()) { |
| 516 if (!recovery_version.Succeeded()) { |
| 517 RecordRecoveryEvent(RECOVERY_FAILED_META_QUERY); |
| 518 } else { |
| 519 RecordRecoveryEvent(RECOVERY_FAILED_META_NO_VERSION); |
| 520 } |
404 return false; | 521 return false; |
| 522 } |
405 | 523 |
| 524 RecordRecoveryEvent(RECOVERY_SUCCESS_META_VERSION); |
406 *version = recovery_version.ColumnInt(0); | 525 *version = recovery_version.ColumnInt(0); |
407 return true; | 526 return true; |
408 } | 527 } |
409 | 528 |
410 } // namespace sql | 529 } // namespace sql |
OLD | NEW |