Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 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 |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "sql/recovery.h" |
| 6 | |
| 7 | #include "base/files/file_path.h" |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 8 | #include "base/format_macros.h" |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 9 | #include "base/logging.h" |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 10 | #include "base/metrics/histogram.h" |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 11 | #include "base/metrics/sparse_histogram.h" |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 12 | #include "base/strings/string_util.h" |
| 13 | #include "base/strings/stringprintf.h" |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 14 | #include "sql/connection.h" |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 15 | #include "sql/statement.h" |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 16 | #include "third_party/sqlite/sqlite3.h" |
| 17 | |
| 18 | namespace sql { |
| 19 | |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 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 | |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 95 | // static |
Torne (Richard Coles) | 4e180b6 | 2013-10-18 15:46:22 +0100 | [diff] [blame] | 96 | bool Recovery::FullRecoverySupported() { |
| 97 | // TODO(shess): See comment in Init(). |
| 98 | #if defined(USE_SYSTEM_SQLITE) |
| 99 | return false; |
| 100 | #else |
| 101 | return true; |
| 102 | #endif |
| 103 | } |
| 104 | |
| 105 | // static |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 106 | scoped_ptr<Recovery> Recovery::Begin( |
| 107 | Connection* connection, |
| 108 | const base::FilePath& db_path) { |
| 109 | scoped_ptr<Recovery> r(new Recovery(connection)); |
| 110 | if (!r->Init(db_path)) { |
| 111 | // TODO(shess): Should Init() failure result in Raze()? |
| 112 | r->Shutdown(POISON); |
| 113 | return scoped_ptr<Recovery>(); |
| 114 | } |
| 115 | |
| 116 | return r.Pass(); |
| 117 | } |
| 118 | |
| 119 | // static |
| 120 | bool Recovery::Recovered(scoped_ptr<Recovery> r) { |
| 121 | return r->Backup(); |
| 122 | } |
| 123 | |
| 124 | // static |
| 125 | void Recovery::Unrecoverable(scoped_ptr<Recovery> r) { |
| 126 | CHECK(r->db_); |
| 127 | // ~Recovery() will RAZE_AND_POISON. |
| 128 | } |
| 129 | |
Torne (Richard Coles) | 68043e1 | 2013-09-26 13:24:57 +0100 | [diff] [blame] | 130 | // static |
| 131 | void Recovery::Rollback(scoped_ptr<Recovery> r) { |
| 132 | // TODO(shess): HISTOGRAM to track? Or just have people crash out? |
| 133 | // Crash and dump? |
| 134 | r->Shutdown(POISON); |
| 135 | } |
| 136 | |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 137 | Recovery::Recovery(Connection* connection) |
| 138 | : db_(connection), |
| 139 | recover_db_() { |
| 140 | // Result should keep the page size specified earlier. |
| 141 | if (db_->page_size_) |
| 142 | recover_db_.set_page_size(db_->page_size_); |
| 143 | |
| 144 | // TODO(shess): This may not handle cases where the default page |
| 145 | // size is used, but the default has changed. I do not think this |
| 146 | // has ever happened. This could be handled by using "PRAGMA |
| 147 | // page_size", at the cost of potential additional failure cases. |
| 148 | } |
| 149 | |
| 150 | Recovery::~Recovery() { |
| 151 | Shutdown(RAZE_AND_POISON); |
| 152 | } |
| 153 | |
| 154 | bool Recovery::Init(const base::FilePath& db_path) { |
| 155 | // Prevent the possibility of re-entering this code due to errors |
| 156 | // which happen while executing this code. |
| 157 | DCHECK(!db_->has_error_callback()); |
| 158 | |
| 159 | // Break any outstanding transactions on the original database to |
| 160 | // prevent deadlocks reading through the attached version. |
| 161 | // TODO(shess): A client may legitimately wish to recover from |
| 162 | // within the transaction context, because it would potentially |
| 163 | // preserve any in-flight changes. Unfortunately, any attach-based |
| 164 | // system could not handle that. A system which manually queried |
| 165 | // one database and stored to the other possibly could, but would be |
| 166 | // more complicated. |
| 167 | db_->RollbackAllTransactions(); |
| 168 | |
Torne (Richard Coles) | 68043e1 | 2013-09-26 13:24:57 +0100 | [diff] [blame] | 169 | // Disable exclusive locking mode so that the attached database can |
| 170 | // access things. The locking_mode change is not active until the |
| 171 | // next database access, so immediately force an access. Enabling |
| 172 | // writable_schema allows processing through certain kinds of |
| 173 | // corruption. |
| 174 | // TODO(shess): It would be better to just close the handle, but it |
| 175 | // is necessary for the final backup which rewrites things. It |
| 176 | // might be reasonable to close then re-open the handle. |
| 177 | ignore_result(db_->Execute("PRAGMA writable_schema=1")); |
| 178 | ignore_result(db_->Execute("PRAGMA locking_mode=NORMAL")); |
| 179 | ignore_result(db_->Execute("SELECT COUNT(*) FROM sqlite_master")); |
| 180 | |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 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); |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 187 | return false; |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 188 | } |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 189 | |
Ben Murdoch | 2385ea3 | 2013-08-06 11:01:04 +0100 | [diff] [blame] | 190 | // TODO(shess): Figure out a story for USE_SYSTEM_SQLITE. The |
| 191 | // virtual table implementation relies on SQLite internals for some |
| 192 | // types and functions, which could be copied inline to make it |
| 193 | // standalone. Or an alternate implementation could try to read |
| 194 | // through errors entirely at the SQLite level. |
| 195 | // |
| 196 | // For now, defer to the caller. The setup will succeed, but the |
| 197 | // later CREATE VIRTUAL TABLE call will fail, at which point the |
| 198 | // caller can fire Unrecoverable(). |
| 199 | #if !defined(USE_SYSTEM_SQLITE) |
| 200 | int rc = recoverVtableInit(recover_db_.db_); |
| 201 | if (rc != SQLITE_OK) { |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 202 | RecordRecoveryEvent(RECOVERY_FAILED_VIRTUAL_TABLE_INIT); |
Ben Murdoch | 2385ea3 | 2013-08-06 11:01:04 +0100 | [diff] [blame] | 203 | LOG(ERROR) << "Failed to initialize recover module: " |
| 204 | << recover_db_.GetErrorMessage(); |
| 205 | return false; |
| 206 | } |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 207 | #else |
| 208 | // If this is infrequent enough, just wire it to Raze(). |
| 209 | RecordRecoveryEvent(RECOVERY_FAILED_VIRTUAL_TABLE_SYSTEM_SQLITE); |
Ben Murdoch | 2385ea3 | 2013-08-06 11:01:04 +0100 | [diff] [blame] | 210 | #endif |
| 211 | |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 212 | // Turn on |SQLITE_RecoveryMode| for the handle, which allows |
| 213 | // reading certain broken databases. |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 214 | if (!recover_db_.Execute("PRAGMA writable_schema=1")) { |
| 215 | RecordRecoveryEvent(RECOVERY_FAILED_WRITABLE_SCHEMA); |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 216 | return false; |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 217 | } |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 218 | |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 219 | if (!recover_db_.AttachDatabase(db_path, "corrupt")) { |
| 220 | RecordRecoveryEvent(RECOVERY_FAILED_ATTACH); |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 221 | return false; |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 222 | } |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 223 | |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 224 | RecordRecoveryEvent(RECOVERY_SUCCESS_INIT); |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 225 | return true; |
| 226 | } |
| 227 | |
| 228 | bool Recovery::Backup() { |
| 229 | CHECK(db_); |
| 230 | CHECK(recover_db_.is_open()); |
| 231 | |
| 232 | // TODO(shess): Some of the failure cases here may need further |
| 233 | // exploration. Just as elsewhere, persistent problems probably |
| 234 | // need to be razed, while anything which might succeed on a future |
| 235 | // run probably should be allowed to try. But since Raze() uses the |
| 236 | // same approach, even that wouldn't work when this code fails. |
| 237 | // |
| 238 | // The documentation for the backup system indicate a relatively |
| 239 | // small number of errors are expected: |
| 240 | // SQLITE_BUSY - cannot lock the destination database. This should |
| 241 | // only happen if someone has another handle to the |
| 242 | // database, Chromium generally doesn't do that. |
| 243 | // SQLITE_LOCKED - someone locked the source database. Should be |
| 244 | // impossible (perhaps anti-virus could?). |
| 245 | // SQLITE_READONLY - destination is read-only. |
| 246 | // SQLITE_IOERR - since source database is temporary, probably |
| 247 | // indicates that the destination contains blocks |
| 248 | // throwing errors, or gross filesystem errors. |
| 249 | // SQLITE_NOMEM - out of memory, should be transient. |
| 250 | // |
| 251 | // AFAICT, SQLITE_BUSY and SQLITE_NOMEM could perhaps be considered |
| 252 | // transient, with SQLITE_LOCKED being unclear. |
| 253 | // |
| 254 | // SQLITE_READONLY and SQLITE_IOERR are probably persistent, with a |
| 255 | // strong chance that Raze() would not resolve them. If Delete() |
| 256 | // deletes the database file, the code could then re-open the file |
| 257 | // and attempt the backup again. |
| 258 | // |
| 259 | // For now, this code attempts a best effort and records histograms |
| 260 | // to inform future development. |
| 261 | |
| 262 | // Backup the original db from the recovered db. |
| 263 | const char* kMain = "main"; |
| 264 | sqlite3_backup* backup = sqlite3_backup_init(db_->db_, kMain, |
| 265 | recover_db_.db_, kMain); |
| 266 | if (!backup) { |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 267 | RecordRecoveryEvent(RECOVERY_FAILED_BACKUP_INIT); |
| 268 | |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 269 | // Error code is in the destination database handle. |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 270 | int err = sqlite3_extended_errcode(db_->db_); |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 271 | UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryHandle", err); |
| 272 | LOG(ERROR) << "sqlite3_backup_init() failed: " |
| 273 | << sqlite3_errmsg(db_->db_); |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 274 | |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 275 | return false; |
| 276 | } |
| 277 | |
| 278 | // -1 backs up the entire database. |
| 279 | int rc = sqlite3_backup_step(backup, -1); |
| 280 | int pages = sqlite3_backup_pagecount(backup); |
| 281 | // TODO(shess): sqlite3_backup_finish() appears to allow returning a |
| 282 | // different value from sqlite3_backup_step(). Circle back and |
| 283 | // figure out if that can usefully inform the decision of whether to |
| 284 | // retry or not. |
| 285 | sqlite3_backup_finish(backup); |
| 286 | DCHECK_GT(pages, 0); |
| 287 | |
| 288 | if (rc != SQLITE_DONE) { |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 289 | RecordRecoveryEvent(RECOVERY_FAILED_BACKUP_STEP); |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 290 | UMA_HISTOGRAM_SPARSE_SLOWLY("Sqlite.RecoveryStep", rc); |
| 291 | LOG(ERROR) << "sqlite3_backup_step() failed: " |
| 292 | << sqlite3_errmsg(db_->db_); |
| 293 | } |
| 294 | |
| 295 | // The destination database was locked. Give up, but leave the data |
| 296 | // in place. Maybe it won't be locked next time. |
| 297 | if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) { |
| 298 | Shutdown(POISON); |
| 299 | return false; |
| 300 | } |
| 301 | |
| 302 | // Running out of memory should be transient, retry later. |
| 303 | if (rc == SQLITE_NOMEM) { |
| 304 | Shutdown(POISON); |
| 305 | return false; |
| 306 | } |
| 307 | |
| 308 | // TODO(shess): For now, leave the original database alone, pending |
| 309 | // results from Sqlite.RecoveryStep. Some errors should probably |
| 310 | // route to RAZE_AND_POISON. |
| 311 | if (rc != SQLITE_DONE) { |
| 312 | Shutdown(POISON); |
| 313 | return false; |
| 314 | } |
| 315 | |
| 316 | // Clean up the recovery db, and terminate the main database |
| 317 | // connection. |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 318 | RecordRecoveryEvent(RECOVERY_SUCCESS_BACKUP); |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 319 | Shutdown(POISON); |
| 320 | return true; |
| 321 | } |
| 322 | |
| 323 | void Recovery::Shutdown(Recovery::Disposition raze) { |
| 324 | if (!db_) |
| 325 | return; |
| 326 | |
| 327 | recover_db_.Close(); |
| 328 | if (raze == RAZE_AND_POISON) { |
| 329 | db_->RazeAndClose(); |
| 330 | } else if (raze == POISON) { |
| 331 | db_->Poison(); |
| 332 | } |
| 333 | db_ = NULL; |
| 334 | } |
| 335 | |
Torne (Richard Coles) | f2477e0 | 2013-11-28 11:55:43 +0000 | [diff] [blame] | 336 | bool Recovery::AutoRecoverTable(const char* table_name, |
| 337 | size_t extend_columns, |
| 338 | size_t* rows_recovered) { |
| 339 | // Query the info for the recovered table in database [main]. |
| 340 | std::string query( |
| 341 | base::StringPrintf("PRAGMA main.table_info(%s)", table_name)); |
| 342 | Statement s(db()->GetUniqueStatement(query.c_str())); |
| 343 | |
| 344 | // The columns of the recover virtual table. |
| 345 | std::vector<std::string> create_column_decls; |
| 346 | |
| 347 | // The columns to select from the recover virtual table when copying |
| 348 | // to the recovered table. |
| 349 | std::vector<std::string> insert_columns; |
| 350 | |
| 351 | // If PRIMARY KEY is a single INTEGER column, then it is an alias |
| 352 | // for ROWID. The primary key can be compound, so this can only be |
| 353 | // determined after processing all column data and tracking what is |
| 354 | // seen. |pk_column_count| counts the columns in the primary key. |
| 355 | // |rowid_decl| stores the ROWID version of the last INTEGER column |
| 356 | // seen, which is at |rowid_ofs| in |create_column_decls|. |
| 357 | size_t pk_column_count = 0; |
| 358 | size_t rowid_ofs; // Only valid if rowid_decl is set. |
| 359 | std::string rowid_decl; // ROWID version of column |rowid_ofs|. |
| 360 | |
| 361 | while (s.Step()) { |
| 362 | const std::string column_name(s.ColumnString(1)); |
| 363 | const std::string column_type(s.ColumnString(2)); |
| 364 | const bool not_null = s.ColumnBool(3); |
| 365 | const int default_type = s.ColumnType(4); |
| 366 | const bool default_is_null = (default_type == COLUMN_TYPE_NULL); |
| 367 | const int pk_column = s.ColumnInt(5); |
| 368 | |
| 369 | if (pk_column > 0) { |
| 370 | // TODO(shess): http://www.sqlite.org/pragma.html#pragma_table_info |
| 371 | // documents column 5 as the index of the column in the primary key |
| 372 | // (zero for not in primary key). I find that it is always 1 for |
| 373 | // columns in the primary key. Since this code is very dependent on |
| 374 | // that pragma, review if the implementation changes. |
| 375 | DCHECK_EQ(pk_column, 1); |
| 376 | ++pk_column_count; |
| 377 | } |
| 378 | |
| 379 | // Construct column declaration as "name type [optional constraint]". |
| 380 | std::string column_decl = column_name; |
| 381 | |
| 382 | // SQLite's affinity detection is documented at: |
| 383 | // http://www.sqlite.org/datatype3.html#affname |
| 384 | // The gist of it is that CHAR, TEXT, and INT use substring matches. |
| 385 | if (column_type.find("INT") != std::string::npos) { |
| 386 | if (pk_column == 1) { |
| 387 | rowid_ofs = create_column_decls.size(); |
| 388 | rowid_decl = column_name + " ROWID"; |
| 389 | } |
| 390 | column_decl += " INTEGER"; |
| 391 | } else if (column_type.find("CHAR") != std::string::npos || |
| 392 | column_type.find("TEXT") != std::string::npos) { |
| 393 | column_decl += " TEXT"; |
| 394 | } else if (column_type == "BLOB") { |
| 395 | column_decl += " BLOB"; |
| 396 | } else { |
| 397 | // TODO(shess): AFAICT, there remain: |
| 398 | // - contains("CLOB") -> TEXT |
| 399 | // - contains("REAL") -> REAL |
| 400 | // - contains("FLOA") -> REAL |
| 401 | // - contains("DOUB") -> REAL |
| 402 | // - other -> "NUMERIC" |
| 403 | // Just code those in as they come up. |
| 404 | NOTREACHED() << " Unsupported type " << column_type; |
| 405 | RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_UNRECOGNIZED_TYPE); |
| 406 | return false; |
| 407 | } |
| 408 | |
| 409 | // If column has constraint "NOT NULL", then inserting NULL into |
| 410 | // that column will fail. If the column has a non-NULL DEFAULT |
| 411 | // specified, the INSERT will handle it (see below). If the |
| 412 | // DEFAULT is also NULL, the row must be filtered out. |
| 413 | // TODO(shess): The above scenario applies to INSERT OR REPLACE, |
| 414 | // whereas INSERT OR IGNORE drops such rows. |
| 415 | // http://www.sqlite.org/lang_conflict.html |
| 416 | if (not_null && default_is_null) |
| 417 | column_decl += " NOT NULL"; |
| 418 | |
| 419 | create_column_decls.push_back(column_decl); |
| 420 | |
| 421 | // Per the NOTE in the header file, convert NULL values to the |
| 422 | // DEFAULT. All columns could be IFNULL(column_name,default), but |
| 423 | // the NULL case would require special handling either way. |
| 424 | if (default_is_null) { |
| 425 | insert_columns.push_back(column_name); |
| 426 | } else { |
| 427 | // The default value appears to be pre-quoted, as if it is |
| 428 | // literally from the sqlite_master CREATE statement. |
| 429 | std::string default_value = s.ColumnString(4); |
| 430 | insert_columns.push_back(base::StringPrintf( |
| 431 | "IFNULL(%s,%s)", column_name.c_str(), default_value.c_str())); |
| 432 | } |
| 433 | } |
| 434 | |
| 435 | // Receiving no column information implies that the table doesn't exist. |
| 436 | if (create_column_decls.empty()) { |
| 437 | RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_MISSING_TABLE); |
| 438 | return false; |
| 439 | } |
| 440 | |
| 441 | // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID. |
| 442 | if (pk_column_count == 1 && !rowid_decl.empty()) |
| 443 | create_column_decls[rowid_ofs] = rowid_decl; |
| 444 | |
| 445 | // Additional columns accept anything. |
| 446 | // TODO(shess): ignoreN isn't well namespaced. But it will fail to |
| 447 | // execute in case of conflicts. |
| 448 | for (size_t i = 0; i < extend_columns; ++i) { |
| 449 | create_column_decls.push_back( |
| 450 | base::StringPrintf("ignore%" PRIuS " ANY", i)); |
| 451 | } |
| 452 | |
| 453 | std::string recover_create(base::StringPrintf( |
| 454 | "CREATE VIRTUAL TABLE temp.recover_%s USING recover(corrupt.%s, %s)", |
| 455 | table_name, |
| 456 | table_name, |
| 457 | JoinString(create_column_decls, ',').c_str())); |
| 458 | |
| 459 | std::string recover_insert(base::StringPrintf( |
| 460 | "INSERT OR REPLACE INTO main.%s SELECT %s FROM temp.recover_%s", |
| 461 | table_name, |
| 462 | JoinString(insert_columns, ',').c_str(), |
| 463 | table_name)); |
| 464 | |
| 465 | std::string recover_drop(base::StringPrintf( |
| 466 | "DROP TABLE temp.recover_%s", table_name)); |
| 467 | |
| 468 | if (!db()->Execute(recover_create.c_str())) { |
| 469 | RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_CREATE); |
| 470 | return false; |
| 471 | } |
| 472 | |
| 473 | if (!db()->Execute(recover_insert.c_str())) { |
| 474 | RecordRecoveryEvent(RECOVERY_FAILED_AUTORECOVER_INSERT); |
| 475 | ignore_result(db()->Execute(recover_drop.c_str())); |
| 476 | return false; |
| 477 | } |
| 478 | |
| 479 | *rows_recovered = db()->GetLastChangeCount(); |
| 480 | |
| 481 | // TODO(shess): Is leaving the recover table around a breaker? |
| 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; |
| 488 | } |
| 489 | |
| 490 | bool Recovery::SetupMeta() { |
| 491 | const char kCreateSql[] = |
| 492 | "CREATE VIRTUAL TABLE temp.recover_meta USING recover" |
| 493 | "(" |
| 494 | "corrupt.meta," |
| 495 | "key TEXT NOT NULL," |
| 496 | "value ANY" // Whatever is stored. |
| 497 | ")"; |
| 498 | if (!db()->Execute(kCreateSql)) { |
| 499 | RecordRecoveryEvent(RECOVERY_FAILED_META_CREATE); |
| 500 | return false; |
| 501 | } |
| 502 | RecordRecoveryEvent(RECOVERY_SUCCESS_SETUP_META); |
| 503 | return true; |
| 504 | } |
| 505 | |
| 506 | bool Recovery::GetMetaVersionNumber(int* version) { |
| 507 | DCHECK(version); |
| 508 | // TODO(shess): DCHECK(db()->DoesTableExist("temp.recover_meta")); |
| 509 | // Unfortunately, DoesTableExist() queries sqlite_master, not |
| 510 | // sqlite_temp_master. |
| 511 | |
| 512 | const char kVersionSql[] = |
| 513 | "SELECT value FROM temp.recover_meta WHERE key = 'version'"; |
| 514 | sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql)); |
| 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 | } |
| 521 | return false; |
| 522 | } |
| 523 | |
| 524 | RecordRecoveryEvent(RECOVERY_SUCCESS_META_VERSION); |
| 525 | *version = recovery_version.ColumnInt(0); |
| 526 | return true; |
| 527 | } |
| 528 | |
Ben Murdoch | ca12bfa | 2013-07-23 11:17:05 +0100 | [diff] [blame] | 529 | } // namespace sql |