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/logging.h" | 9 #include "base/logging.h" |
9 #include "base/metrics/sparse_histogram.h" | 10 #include "base/metrics/sparse_histogram.h" |
| 11 #include "base/strings/string_util.h" |
| 12 #include "base/strings/stringprintf.h" |
10 #include "sql/connection.h" | 13 #include "sql/connection.h" |
| 14 #include "sql/statement.h" |
11 #include "third_party/sqlite/sqlite3.h" | 15 #include "third_party/sqlite/sqlite3.h" |
12 | 16 |
13 namespace sql { | 17 namespace sql { |
14 | 18 |
15 // static | 19 // static |
16 bool Recovery::FullRecoverySupported() { | 20 bool Recovery::FullRecoverySupported() { |
17 // TODO(shess): See comment in Init(). | 21 // TODO(shess): See comment in Init(). |
18 #if defined(USE_SYSTEM_SQLITE) | 22 #if defined(USE_SYSTEM_SQLITE) |
19 return false; | 23 return false; |
20 #else | 24 #else |
(...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
226 | 230 |
227 recover_db_.Close(); | 231 recover_db_.Close(); |
228 if (raze == RAZE_AND_POISON) { | 232 if (raze == RAZE_AND_POISON) { |
229 db_->RazeAndClose(); | 233 db_->RazeAndClose(); |
230 } else if (raze == POISON) { | 234 } else if (raze == POISON) { |
231 db_->Poison(); | 235 db_->Poison(); |
232 } | 236 } |
233 db_ = NULL; | 237 db_ = NULL; |
234 } | 238 } |
235 | 239 |
| 240 bool Recovery::AutoRecoverTable(const char* table_name, |
| 241 size_t extend_columns, |
| 242 size_t* rows_recovered) { |
| 243 // Query the info for the recovered table in database [main]. |
| 244 std::string query( |
| 245 base::StringPrintf("PRAGMA main.table_info(%s)", table_name)); |
| 246 Statement s(db()->GetUniqueStatement(query.c_str())); |
| 247 |
| 248 // The columns of the recover virtual table. |
| 249 std::vector<std::string> create_column_decls; |
| 250 |
| 251 // The columns to select from the recover virtual table when copying |
| 252 // to the recovered table. |
| 253 std::vector<std::string> insert_columns; |
| 254 |
| 255 // If PRIMARY KEY is a single INTEGER column, then it is an alias |
| 256 // for ROWID. The primary key can be compound, so this can only be |
| 257 // determined after processing all column data and tracking what is |
| 258 // seen. |pk_column_count| counts the columns in the primary key. |
| 259 // |rowid_decl| stores the ROWID version of the last INTEGER column |
| 260 // seen, which is at |rowid_ofs| in |create_column_decls|. |
| 261 size_t pk_column_count = 0; |
| 262 size_t rowid_ofs; // Only valid if rowid_decl is set. |
| 263 std::string rowid_decl; // ROWID version of column |rowid_ofs|. |
| 264 |
| 265 while (s.Step()) { |
| 266 const std::string column_name(s.ColumnString(1)); |
| 267 const std::string column_type(s.ColumnString(2)); |
| 268 const bool not_null = s.ColumnBool(3); |
| 269 const int default_type = s.ColumnType(4); |
| 270 const bool default_is_null = (default_type == COLUMN_TYPE_NULL); |
| 271 const int pk_column = s.ColumnInt(5); |
| 272 |
| 273 if (pk_column > 0) { |
| 274 // TODO(shess): http://www.sqlite.org/pragma.html#pragma_table_info |
| 275 // documents column 5 as the index of the column in the primary key |
| 276 // (zero for not in primary key). I find that it is always 1 for |
| 277 // columns in the primary key. Since this code is very dependent on |
| 278 // that pragma, review if the implementation changes. |
| 279 DCHECK_EQ(pk_column, 1); |
| 280 ++pk_column_count; |
| 281 } |
| 282 |
| 283 // Construct column declaration as "name type [optional constraint]". |
| 284 std::string column_decl = column_name; |
| 285 |
| 286 // SQLite's affinity detection is documented at: |
| 287 // http://www.sqlite.org/datatype3.html#affname |
| 288 // The gist of it is that CHAR, TEXT, and INT use substring matches. |
| 289 if (column_type.find("INT") != std::string::npos) { |
| 290 if (pk_column == 1) { |
| 291 rowid_ofs = create_column_decls.size(); |
| 292 rowid_decl = column_name + " ROWID"; |
| 293 } |
| 294 column_decl += " INTEGER"; |
| 295 } else if (column_type.find("CHAR") != std::string::npos || |
| 296 column_type.find("TEXT") != std::string::npos) { |
| 297 column_decl += " TEXT"; |
| 298 } else if (column_type == "BLOB") { |
| 299 column_decl += " BLOB"; |
| 300 } else { |
| 301 // TODO(shess): AFAICT, there remain: |
| 302 // - contains("CLOB") -> TEXT |
| 303 // - contains("REAL") -> REAL |
| 304 // - contains("FLOA") -> REAL |
| 305 // - contains("DOUB") -> REAL |
| 306 // - other -> "NUMERIC" |
| 307 // Just code those in as they come up. |
| 308 NOTREACHED() << " Unsupported type " << column_type; |
| 309 return false; |
| 310 } |
| 311 |
| 312 // If column has constraint "NOT NULL", then inserting NULL into |
| 313 // that column will fail. If the column has a non-NULL DEFAULT |
| 314 // specified, the INSERT will handle it (see below). If the |
| 315 // DEFAULT is also NULL, the row must be filtered out. |
| 316 // TODO(shess): The above scenario applies to INSERT OR REPLACE, |
| 317 // whereas INSERT OR IGNORE drops such rows. |
| 318 // http://www.sqlite.org/lang_conflict.html |
| 319 if (not_null && default_is_null) |
| 320 column_decl += " NOT NULL"; |
| 321 |
| 322 create_column_decls.push_back(column_decl); |
| 323 |
| 324 // Per the NOTE in the header file, convert NULL values to the |
| 325 // DEFAULT. All columns could be IFNULL(column_name,default), but |
| 326 // the NULL case would require special handling either way. |
| 327 if (default_is_null) { |
| 328 insert_columns.push_back(column_name); |
| 329 } else { |
| 330 // The default value appears to be pre-quoted, as if it is |
| 331 // literally from the sqlite_master CREATE statement. |
| 332 std::string default_value = s.ColumnString(4); |
| 333 insert_columns.push_back(base::StringPrintf( |
| 334 "IFNULL(%s,%s)", column_name.c_str(), default_value.c_str())); |
| 335 } |
| 336 } |
| 337 |
| 338 // Receiving no column information implies that the table doesn't exist. |
| 339 if (create_column_decls.empty()) |
| 340 return false; |
| 341 |
| 342 // If the PRIMARY KEY was a single INTEGER column, convert it to ROWID. |
| 343 if (pk_column_count == 1 && !rowid_decl.empty()) |
| 344 create_column_decls[rowid_ofs] = rowid_decl; |
| 345 |
| 346 // Additional columns accept anything. |
| 347 // TODO(shess): ignoreN isn't well namespaced. But it will fail to |
| 348 // execute in case of conflicts. |
| 349 for (size_t i = 0; i < extend_columns; ++i) { |
| 350 create_column_decls.push_back( |
| 351 base::StringPrintf("ignore%" PRIuS " ANY", i)); |
| 352 } |
| 353 |
| 354 std::string recover_create(base::StringPrintf( |
| 355 "CREATE VIRTUAL TABLE temp.recover_%s USING recover(corrupt.%s, %s)", |
| 356 table_name, |
| 357 table_name, |
| 358 JoinString(create_column_decls, ',').c_str())); |
| 359 |
| 360 std::string recover_insert(base::StringPrintf( |
| 361 "INSERT OR REPLACE INTO main.%s SELECT %s FROM temp.recover_%s", |
| 362 table_name, |
| 363 JoinString(insert_columns, ',').c_str(), |
| 364 table_name)); |
| 365 |
| 366 std::string recover_drop(base::StringPrintf( |
| 367 "DROP TABLE temp.recover_%s", table_name)); |
| 368 |
| 369 if (!db()->Execute(recover_create.c_str())) |
| 370 return false; |
| 371 |
| 372 if (!db()->Execute(recover_insert.c_str())) { |
| 373 ignore_result(db()->Execute(recover_drop.c_str())); |
| 374 return false; |
| 375 } |
| 376 |
| 377 *rows_recovered = db()->GetLastChangeCount(); |
| 378 |
| 379 // TODO(shess): Is leaving the recover table around a breaker? |
| 380 return db()->Execute(recover_drop.c_str()); |
| 381 } |
| 382 |
| 383 bool Recovery::SetupMeta() { |
| 384 const char kCreateSql[] = |
| 385 "CREATE VIRTUAL TABLE temp.recover_meta USING recover" |
| 386 "(" |
| 387 "corrupt.meta," |
| 388 "key TEXT NOT NULL," |
| 389 "value ANY" // Whatever is stored. |
| 390 ")"; |
| 391 return db()->Execute(kCreateSql); |
| 392 } |
| 393 |
| 394 bool Recovery::GetMetaVersionNumber(int* version) { |
| 395 DCHECK(version); |
| 396 // TODO(shess): DCHECK(db()->DoesTableExist("temp.recover_meta")); |
| 397 // Unfortunately, DoesTableExist() queries sqlite_master, not |
| 398 // sqlite_temp_master. |
| 399 |
| 400 const char kVersionSql[] = |
| 401 "SELECT value FROM temp.recover_meta WHERE key = 'version'"; |
| 402 sql::Statement recovery_version(db()->GetUniqueStatement(kVersionSql)); |
| 403 if (!recovery_version.Step()) |
| 404 return false; |
| 405 |
| 406 *version = recovery_version.ColumnInt(0); |
| 407 return true; |
| 408 } |
| 409 |
236 } // namespace sql | 410 } // namespace sql |
OLD | NEW |