1 // Copyright (c) 2012 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 "chrome/browser/history/thumbnail_database.h" 6 7 #include <algorithm> 8 #include <string> 9 10 #include "base/bind.h" 11 #include "base/debug/alias.h" 12 #include "base/debug/dump_without_crashing.h" 13 #include "base/files/file_util.h" 14 #include "base/format_macros.h" 15 #include "base/memory/ref_counted_memory.h" 16 #include "base/metrics/histogram.h" 17 #include "base/rand_util.h" 18 #include "base/strings/string_util.h" 19 #include "base/strings/stringprintf.h" 20 #include "base/time/time.h" 21 #include "components/history/core/browser/history_client.h" 22 #include "components/history/core/browser/url_database.h" 23 #include "sql/recovery.h" 24 #include "sql/statement.h" 25 #include "sql/transaction.h" 26 #include "third_party/sqlite/sqlite3.h" 27 28 #if defined(OS_MACOSX) 29 #include "base/mac/mac_util.h" 30 #endif 31 32 // Description of database tables: 33 // 34 // icon_mapping 35 // id Unique ID. 36 // page_url Page URL which has one or more associated favicons. 37 // icon_id The ID of favicon that this mapping maps to. 38 // 39 // favicons This table associates a row to each favicon for a 40 // |page_url| in the |icon_mapping| table. This is the 41 // default favicon |page_url|/favicon.ico plus any favicons 42 // associated via <link rel="icon_type" href="url">. 43 // The |id| matches the |icon_id| field in the appropriate 44 // row in the icon_mapping table. 45 // 46 // id Unique ID. 47 // url The URL at which the favicon file is located. 48 // icon_type The type of the favicon specified in the rel attribute of 49 // the link tag. The FAVICON type is used for the default 50 // favicon.ico favicon. 51 // 52 // favicon_bitmaps This table contains the PNG encoded bitmap data of the 53 // favicons. There is a separate row for every size in a 54 // multi resolution bitmap. The bitmap data is associated 55 // to the favicon via the |icon_id| field which matches 56 // the |id| field in the appropriate row in the |favicons| 57 // table. 58 // 59 // id Unique ID. 60 // icon_id The ID of the favicon that the bitmap is associated to. 61 // last_updated The time at which this favicon was inserted into the 62 // table. This is used to determine if it needs to be 63 // redownloaded from the web. 64 // image_data PNG encoded data of the favicon. 65 // width Pixel width of |image_data|. 66 // height Pixel height of |image_data|. 67 68 namespace { 69 70 // For this database, schema migrations are deprecated after two 71 // years. This means that the oldest non-deprecated version should be 72 // two years old or greater (thus the migrations to get there are 73 // older). Databases containing deprecated versions will be cleared 74 // at startup. Since this database is a cache, losing old data is not 75 // fatal (in fact, very old data may be expired immediately at startup 76 // anyhow). 77 78 // Version 7: 911a634d/r209424 by qsr (at) chromium.org on 2013-07-01 79 // Version 6: 610f923b/r152367 by pkotwicz (at) chromium.org on 2012-08-20 80 // Version 5: e2ee8ae9/r105004 by groby (at) chromium.org on 2011-10-12 81 // Version 4: 5f104d76/r77288 by sky (at) chromium.org on 2011-03-08 (deprecated) 82 // Version 3: 09911bf3/r15 by initial.commit on 2008-07-26 (deprecated) 83 84 // Version number of the database. 85 // NOTE(shess): When changing the version, add a new golden file for 86 // the new version and a test to verify that Init() works with it. 87 const int kCurrentVersionNumber = 7; 88 const int kCompatibleVersionNumber = 7; 89 const int kDeprecatedVersionNumber = 4; // and earlier. 90 91 void FillIconMapping(const sql::Statement& statement, 92 const GURL& page_url, 93 history::IconMapping* icon_mapping) { 94 icon_mapping->mapping_id = statement.ColumnInt64(0); 95 icon_mapping->icon_id = statement.ColumnInt64(1); 96 icon_mapping->icon_type = 97 static_cast<favicon_base::IconType>(statement.ColumnInt(2)); 98 icon_mapping->icon_url = GURL(statement.ColumnString(3)); 99 icon_mapping->page_url = page_url; 100 } 101 102 enum InvalidStructureType { 103 // NOTE(shess): Intentionally skip bucket 0 to account for 104 // conversion from a boolean histogram. 105 STRUCTURE_EVENT_FAVICON = 1, 106 STRUCTURE_EVENT_VERSION4, 107 STRUCTURE_EVENT_VERSION5, 108 109 // Always keep this at the end. 110 STRUCTURE_EVENT_MAX, 111 }; 112 113 void RecordInvalidStructure(InvalidStructureType invalid_type) { 114 UMA_HISTOGRAM_ENUMERATION("History.InvalidFaviconsDBStructure", 115 invalid_type, STRUCTURE_EVENT_MAX); 116 } 117 118 // Attempt to pass 2000 bytes of |debug_info| into a crash dump. 119 void DumpWithoutCrashing2000(const std::string& debug_info) { 120 char debug_buf[2000]; 121 base::strlcpy(debug_buf, debug_info.c_str(), arraysize(debug_buf)); 122 base::debug::Alias(&debug_buf); 123 124 base::debug::DumpWithoutCrashing(); 125 } 126 127 void ReportCorrupt(sql::Connection* db, size_t startup_kb) { 128 // Buffer for accumulating debugging info about the error. Place 129 // more-relevant information earlier, in case things overflow the 130 // fixed-size buffer. 131 std::string debug_info; 132 133 base::StringAppendF(&debug_info, "SQLITE_CORRUPT, integrity_check:\n"); 134 135 // Check files up to 8M to keep things from blocking too long. 136 const size_t kMaxIntegrityCheckSize = 8192; 137 if (startup_kb > kMaxIntegrityCheckSize) { 138 base::StringAppendF(&debug_info, "too big %" PRIuS "\n", startup_kb); 139 } else { 140 std::vector<std::string> messages; 141 142 const base::TimeTicks before = base::TimeTicks::Now(); 143 db->FullIntegrityCheck(&messages); 144 base::StringAppendF(&debug_info, "# %" PRIx64 " ms, %" PRIuS " records\n", 145 (base::TimeTicks::Now() - before).InMilliseconds(), 146 messages.size()); 147 148 // SQLite returns up to 100 messages by default, trim deeper to 149 // keep close to the 2000-character size limit for dumping. 150 // 151 // TODO(shess): If the first 20 tend to be actionable, test if 152 // passing the count to integrity_check makes it exit earlier. In 153 // that case it may be possible to greatly ease the size 154 // restriction. 155 const size_t kMaxMessages = 20; 156 for (size_t i = 0; i < kMaxMessages && i < messages.size(); ++i) { 157 base::StringAppendF(&debug_info, "%s\n", messages[i].c_str()); 158 } 159 } 160 161 DumpWithoutCrashing2000(debug_info); 162 } 163 164 void ReportError(sql::Connection* db, int error) { 165 // Buffer for accumulating debugging info about the error. Place 166 // more-relevant information earlier, in case things overflow the 167 // fixed-size buffer. 168 std::string debug_info; 169 170 // The error message from the failed operation. 171 base::StringAppendF(&debug_info, "db error: %d/%s\n", 172 db->GetErrorCode(), db->GetErrorMessage()); 173 174 // System errno information. 175 base::StringAppendF(&debug_info, "errno: %d\n", db->GetLastErrno()); 176 177 // SQLITE_ERROR reports seem to be attempts to upgrade invalid 178 // schema, try to log that info. 179 if (error == SQLITE_ERROR) { 180 const char* kVersionSql = "SELECT value FROM meta WHERE key = 'version'"; 181 if (db->IsSQLValid(kVersionSql)) { 182 sql::Statement statement(db->GetUniqueStatement(kVersionSql)); 183 if (statement.Step()) { 184 debug_info += "version: "; 185 debug_info += statement.ColumnString(0); 186 debug_info += '\n'; 187 } else if (statement.Succeeded()) { 188 debug_info += "version: none\n"; 189 } else { 190 debug_info += "version: error\n"; 191 } 192 } else { 193 debug_info += "version: invalid\n"; 194 } 195 196 debug_info += "schema:\n"; 197 198 // sqlite_master has columns: 199 // type - "index" or "table". 200 // name - name of created element. 201 // tbl_name - name of element, or target table in case of index. 202 // rootpage - root page of the element in database file. 203 // sql - SQL to create the element. 204 // In general, the |sql| column is sufficient to derive the other 205 // columns. |rootpage| is not interesting for debugging, without 206 // the contents of the database. The COALESCE is because certain 207 // automatic elements will have a |name| but no |sql|, 208 const char* kSchemaSql = "SELECT COALESCE(sql, name) FROM sqlite_master"; 209 sql::Statement statement(db->GetUniqueStatement(kSchemaSql)); 210 while (statement.Step()) { 211 debug_info += statement.ColumnString(0); 212 debug_info += '\n'; 213 } 214 if (!statement.Succeeded()) 215 debug_info += "error\n"; 216 } 217 218 // TODO(shess): Think of other things to log. Not logging the 219 // statement text because the backtrace should suffice in most 220 // cases. The database schema is a possibility, but the 221 // likelihood of recursive error callbacks makes that risky (same 222 // reasoning applies to other data fetched from the database). 223 224 DumpWithoutCrashing2000(debug_info); 225 } 226 227 // TODO(shess): If this proves out, perhaps lift the code out to 228 // chrome/browser/diagnostics/sqlite_diagnostics.{h,cc}. 229 void GenerateDiagnostics(sql::Connection* db, 230 size_t startup_kb, 231 int extended_error) { 232 int error = (extended_error & 0xFF); 233 234 // Infrequently report information about the error up to the crash 235 // server. 236 static const uint64 kReportsPerMillion = 50000; 237 238 // Since some/most errors will not resolve themselves, only report 239 // once per Chrome run. 240 static bool reported = false; 241 if (reported) 242 return; 243 244 uint64 rand = base::RandGenerator(1000000); 245 if (error == SQLITE_CORRUPT) { 246 // Once the database is known to be corrupt, it will generate a 247 // stream of errors until someone fixes it, so give one chance. 248 // Set first in case of errors in generating the report. 249 reported = true; 250 251 // Corrupt cases currently dominate, report them very infrequently. 252 static const uint64 kCorruptReportsPerMillion = 10000; 253 if (rand < kCorruptReportsPerMillion) 254 ReportCorrupt(db, startup_kb); 255 } else if (error == SQLITE_READONLY) { 256 // SQLITE_READONLY appears similar to SQLITE_CORRUPT - once it 257 // is seen, it is almost guaranteed to be seen again. 258 reported = true; 259 260 if (rand < kReportsPerMillion) 261 ReportError(db, extended_error); 262 } else { 263 // Only set the flag when making a report. This should allow 264 // later (potentially different) errors in a stream of errors to 265 // be reported. 266 // 267 // TODO(shess): Would it be worthwile to audit for which cases 268 // want once-only handling? Sqlite.Error.Thumbnail shows 269 // CORRUPT and READONLY as almost 95% of all reports on these 270 // channels, so probably easier to just harvest from the field. 271 if (rand < kReportsPerMillion) { 272 reported = true; 273 ReportError(db, extended_error); 274 } 275 } 276 } 277 278 // NOTE(shess): Schema modifications must consider initial creation in 279 // |InitImpl()|, recovery in |RecoverDatabaseOrRaze()|, and history pruning in 280 // |RetainDataForPageUrls()|. 281 bool InitTables(sql::Connection* db) { 282 const char kIconMappingSql[] = 283 "CREATE TABLE IF NOT EXISTS icon_mapping" 284 "(" 285 "id INTEGER PRIMARY KEY," 286 "page_url LONGVARCHAR NOT NULL," 287 "icon_id INTEGER" 288 ")"; 289 if (!db->Execute(kIconMappingSql)) 290 return false; 291 292 const char kFaviconsSql[] = 293 "CREATE TABLE IF NOT EXISTS favicons" 294 "(" 295 "id INTEGER PRIMARY KEY," 296 "url LONGVARCHAR NOT NULL," 297 // default icon_type FAVICON to be consistent with past migration. 298 "icon_type INTEGER DEFAULT 1" 299 ")"; 300 if (!db->Execute(kFaviconsSql)) 301 return false; 302 303 const char kFaviconBitmapsSql[] = 304 "CREATE TABLE IF NOT EXISTS favicon_bitmaps" 305 "(" 306 "id INTEGER PRIMARY KEY," 307 "icon_id INTEGER NOT NULL," 308 "last_updated INTEGER DEFAULT 0," 309 "image_data BLOB," 310 "width INTEGER DEFAULT 0," 311 "height INTEGER DEFAULT 0" 312 ")"; 313 if (!db->Execute(kFaviconBitmapsSql)) 314 return false; 315 316 return true; 317 } 318 319 // NOTE(shess): Schema modifications must consider initial creation in 320 // |InitImpl()|, recovery in |RecoverDatabaseOrRaze()|, and history pruning in 321 // |RetainDataForPageUrls()|. 322 bool InitIndices(sql::Connection* db) { 323 const char kIconMappingUrlIndexSql[] = 324 "CREATE INDEX IF NOT EXISTS icon_mapping_page_url_idx" 325 " ON icon_mapping(page_url)"; 326 const char kIconMappingIdIndexSql[] = 327 "CREATE INDEX IF NOT EXISTS icon_mapping_icon_id_idx" 328 " ON icon_mapping(icon_id)"; 329 if (!db->Execute(kIconMappingUrlIndexSql) || 330 !db->Execute(kIconMappingIdIndexSql)) { 331 return false; 332 } 333 334 const char kFaviconsIndexSql[] = 335 "CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)"; 336 if (!db->Execute(kFaviconsIndexSql)) 337 return false; 338 339 const char kFaviconBitmapsIndexSql[] = 340 "CREATE INDEX IF NOT EXISTS favicon_bitmaps_icon_id ON " 341 "favicon_bitmaps(icon_id)"; 342 if (!db->Execute(kFaviconBitmapsIndexSql)) 343 return false; 344 345 return true; 346 } 347 348 enum RecoveryEventType { 349 RECOVERY_EVENT_RECOVERED = 0, 350 RECOVERY_EVENT_FAILED_SCOPER, 351 RECOVERY_EVENT_FAILED_META_VERSION_ERROR, // obsolete 352 RECOVERY_EVENT_FAILED_META_VERSION_NONE, // obsolete 353 RECOVERY_EVENT_FAILED_META_WRONG_VERSION6, // obsolete 354 RECOVERY_EVENT_FAILED_META_WRONG_VERSION5, // obsolete 355 RECOVERY_EVENT_FAILED_META_WRONG_VERSION, 356 RECOVERY_EVENT_FAILED_RECOVER_META, // obsolete 357 RECOVERY_EVENT_FAILED_META_INSERT, // obsolete 358 RECOVERY_EVENT_FAILED_INIT, 359 RECOVERY_EVENT_FAILED_RECOVER_FAVICONS, // obsolete 360 RECOVERY_EVENT_FAILED_FAVICONS_INSERT, // obsolete 361 RECOVERY_EVENT_FAILED_RECOVER_FAVICON_BITMAPS, // obsolete 362 RECOVERY_EVENT_FAILED_FAVICON_BITMAPS_INSERT, // obsolete 363 RECOVERY_EVENT_FAILED_RECOVER_ICON_MAPPING, // obsolete 364 RECOVERY_EVENT_FAILED_ICON_MAPPING_INSERT, // obsolete 365 RECOVERY_EVENT_RECOVERED_VERSION6, // obsolete 366 RECOVERY_EVENT_FAILED_META_INIT, 367 RECOVERY_EVENT_FAILED_META_VERSION, 368 RECOVERY_EVENT_DEPRECATED, 369 RECOVERY_EVENT_FAILED_V5_INITSCHEMA, // obsolete 370 RECOVERY_EVENT_FAILED_V5_AUTORECOVER_FAVICONS, // obsolete 371 RECOVERY_EVENT_FAILED_V5_AUTORECOVER_ICON_MAPPING, // obsolete 372 RECOVERY_EVENT_RECOVERED_VERSION5, // obsolete 373 RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS, 374 RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS, 375 RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING, 376 RECOVERY_EVENT_FAILED_COMMIT, 377 378 // Always keep this at the end. 379 RECOVERY_EVENT_MAX, 380 }; 381 382 void RecordRecoveryEvent(RecoveryEventType recovery_event) { 383 UMA_HISTOGRAM_ENUMERATION("History.FaviconsRecovery", 384 recovery_event, RECOVERY_EVENT_MAX); 385 } 386 387 // Recover the database to the extent possible, razing it if recovery 388 // is not possible. 389 // TODO(shess): This is mostly just a safe proof of concept. In the 390 // real world, this database is probably not worthwhile recovering, as 391 // opposed to just razing it and starting over whenever corruption is 392 // detected. So this database is a good test subject. 393 void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) { 394 // NOTE(shess): This code is currently specific to the version 395 // number. I am working on simplifying things to loosen the 396 // dependency, meanwhile contact me if you need to bump the version. 397 DCHECK_EQ(7, kCurrentVersionNumber); 398 399 // TODO(shess): Reset back after? 400 db->reset_error_callback(); 401 402 // For histogram purposes. 403 size_t favicons_rows_recovered = 0; 404 size_t favicon_bitmaps_rows_recovered = 0; 405 size_t icon_mapping_rows_recovered = 0; 406 int64 original_size = 0; 407 base::GetFileSize(db_path, &original_size); 408 409 scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path); 410 if (!recovery) { 411 // TODO(shess): Unable to create recovery connection. This 412 // implies something substantial is wrong. At this point |db| has 413 // been poisoned so there is nothing really to do. 414 // 415 // Possible responses are unclear. If the failure relates to a 416 // problem somehow specific to the temporary file used to back the 417 // database, then an in-memory database could possibly be used. 418 // This could potentially allow recovering the main database, and 419 // might be simple to implement w/in Begin(). 420 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_SCOPER); 421 return; 422 } 423 424 // Setup the meta recovery table and fetch the version number from 425 // the corrupt database. 426 int version = 0; 427 if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) { 428 // TODO(shess): Prior histograms indicate all failures are in 429 // creating the recover virtual table for corrupt.meta. The table 430 // may not exist, or the database may be too far gone. Either 431 // way, unclear how to resolve. 432 sql::Recovery::Rollback(recovery.Pass()); 433 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_VERSION); 434 return; 435 } 436 437 // This code may be able to fetch version information that the regular 438 // deprecation path cannot. 439 // NOTE(shess): v5 and v6 are currently not deprecated in the normal Init() 440 // path, but are deprecated in the recovery path in the interest of keeping 441 // the code simple. http://crbug.com/327485 for numbers. 442 DCHECK_LE(kDeprecatedVersionNumber, 6); 443 if (version <= 6) { 444 sql::Recovery::Unrecoverable(recovery.Pass()); 445 RecordRecoveryEvent(RECOVERY_EVENT_DEPRECATED); 446 return; 447 } 448 449 // Earlier versions have been handled or deprecated, later versions should be 450 // impossible. 451 if (version != 7) { 452 sql::Recovery::Unrecoverable(recovery.Pass()); 453 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION); 454 return; 455 } 456 457 // Recover to current schema version. 458 sql::MetaTable recover_meta_table; 459 if (!recover_meta_table.Init(recovery->db(), kCurrentVersionNumber, 460 kCompatibleVersionNumber)) { 461 sql::Recovery::Rollback(recovery.Pass()); 462 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT); 463 return; 464 } 465 466 // Create a fresh version of the database. The recovery code uses 467 // conflict-resolution to handle duplicates, so the indices are 468 // necessary. 469 if (!InitTables(recovery->db()) || !InitIndices(recovery->db())) { 470 // TODO(shess): Unable to create the new schema in the new 471 // database. The new database should be a temporary file, so 472 // being unable to work with it is pretty unclear. 473 // 474 // What are the potential responses, even? The recovery database 475 // could be opened as in-memory. If the temp database had a 476 // filesystem problem and the temp filesystem differs from the 477 // main database, then that could fix it. 478 sql::Recovery::Rollback(recovery.Pass()); 479 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_INIT); 480 return; 481 } 482 483 if (!recovery->AutoRecoverTable("favicons", 0, &favicons_rows_recovered)) { 484 sql::Recovery::Rollback(recovery.Pass()); 485 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICONS); 486 return; 487 } 488 if (!recovery->AutoRecoverTable("favicon_bitmaps", 0, 489 &favicon_bitmaps_rows_recovered)) { 490 sql::Recovery::Rollback(recovery.Pass()); 491 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_FAVICON_BITMAPS); 492 return; 493 } 494 if (!recovery->AutoRecoverTable("icon_mapping", 0, 495 &icon_mapping_rows_recovered)) { 496 sql::Recovery::Rollback(recovery.Pass()); 497 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_ICON_MAPPING); 498 return; 499 } 500 501 // TODO(shess): Is it possible/likely to have broken foreign-key 502 // issues with the tables? 503 // - icon_mapping.icon_id maps to no favicons.id 504 // - favicon_bitmaps.icon_id maps to no favicons.id 505 // - favicons.id is referenced by no icon_mapping.icon_id 506 // - favicons.id is referenced by no favicon_bitmaps.icon_id 507 // This step is possibly not worth the effort necessary to develop 508 // and sequence the statements, as it is basically a form of garbage 509 // collection. 510 511 if (!sql::Recovery::Recovered(recovery.Pass())) { 512 RecordRecoveryEvent(RECOVERY_EVENT_FAILED_COMMIT); 513 return; 514 } 515 516 // Track the size of the recovered database relative to the size of 517 // the input database. The size should almost always be smaller, 518 // unless the input database was empty to start with. If the 519 // percentage results are very low, something is awry. 520 int64 final_size = 0; 521 if (original_size > 0 && 522 base::GetFileSize(db_path, &final_size) && 523 final_size > 0) { 524 int percentage = static_cast<int>(original_size * 100 / final_size); 525 UMA_HISTOGRAM_PERCENTAGE("History.FaviconsRecoveredPercentage", 526 std::max(100, percentage)); 527 } 528 529 // Using 10,000 because these cases mostly care about "none 530 // recovered" and "lots recovered". More than 10,000 rows recovered 531 // probably means there's something wrong with the profile. 532 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFavicons", 533 favicons_rows_recovered); 534 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsFaviconBitmaps", 535 favicon_bitmaps_rows_recovered); 536 UMA_HISTOGRAM_COUNTS_10000("History.FaviconsRecoveredRowsIconMapping", 537 icon_mapping_rows_recovered); 538 539 RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED); 540 } 541 542 void DatabaseErrorCallback(sql::Connection* db, 543 const base::FilePath& db_path, 544 size_t startup_kb, 545 history::HistoryClient* history_client, 546 int extended_error, 547 sql::Statement* stmt) { 548 // TODO(shess): Assert that this is running on a safe thread. 549 // AFAICT, should be the history thread, but at this level I can't 550 // see how to reach that. 551 552 if (history_client && history_client->ShouldReportDatabaseError()) { 553 GenerateDiagnostics(db, startup_kb, extended_error); 554 } 555 556 // Attempt to recover corrupt databases. 557 int error = (extended_error & 0xFF); 558 if (error == SQLITE_CORRUPT || 559 error == SQLITE_CANTOPEN || 560 error == SQLITE_NOTADB) { 561 RecoverDatabaseOrRaze(db, db_path); 562 } 563 564 // The default handling is to assert on debug and to ignore on release. 565 if (!sql::Connection::ShouldIgnoreSqliteError(extended_error)) 566 DLOG(FATAL) << db->GetErrorMessage(); 567 } 568 569 } // namespace 570 571 namespace history { 572 573 ThumbnailDatabase::IconMappingEnumerator::IconMappingEnumerator() { 574 } 575 576 ThumbnailDatabase::IconMappingEnumerator::~IconMappingEnumerator() { 577 } 578 579 bool ThumbnailDatabase::IconMappingEnumerator::GetNextIconMapping( 580 IconMapping* icon_mapping) { 581 if (!statement_.Step()) 582 return false; 583 FillIconMapping(statement_, GURL(statement_.ColumnString(4)), icon_mapping); 584 return true; 585 } 586 587 ThumbnailDatabase::ThumbnailDatabase(HistoryClient* history_client) 588 : history_client_(history_client) { 589 } 590 591 ThumbnailDatabase::~ThumbnailDatabase() { 592 // The DBCloseScoper will delete the DB and the cache. 593 } 594 595 sql::InitStatus ThumbnailDatabase::Init(const base::FilePath& db_name) { 596 // TODO(shess): Consider separating database open from schema setup. 597 // With that change, this code could Raze() from outside the 598 // transaction, rather than needing RazeAndClose() in InitImpl(). 599 600 // Retry failed setup in case the recovery system fixed things. 601 const size_t kAttempts = 2; 602 603 sql::InitStatus status = sql::INIT_FAILURE; 604 for (size_t i = 0; i < kAttempts; ++i) { 605 status = InitImpl(db_name); 606 if (status == sql::INIT_OK) 607 return status; 608 609 meta_table_.Reset(); 610 db_.Close(); 611 } 612 return status; 613 } 614 615 void ThumbnailDatabase::ComputeDatabaseMetrics() { 616 sql::Statement favicon_count( 617 db_.GetCachedStatement(SQL_FROM_HERE, "SELECT COUNT(*) FROM favicons")); 618 UMA_HISTOGRAM_COUNTS_10000( 619 "History.NumFaviconsInDB", 620 favicon_count.Step() ? favicon_count.ColumnInt(0) : 0); 621 } 622 623 void ThumbnailDatabase::BeginTransaction() { 624 db_.BeginTransaction(); 625 } 626 627 void ThumbnailDatabase::CommitTransaction() { 628 db_.CommitTransaction(); 629 } 630 631 void ThumbnailDatabase::RollbackTransaction() { 632 db_.RollbackTransaction(); 633 } 634 635 void ThumbnailDatabase::Vacuum() { 636 DCHECK(db_.transaction_nesting() == 0) << 637 "Can not have a transaction when vacuuming."; 638 ignore_result(db_.Execute("VACUUM")); 639 } 640 641 void ThumbnailDatabase::TrimMemory(bool aggressively) { 642 db_.TrimMemory(aggressively); 643 } 644 645 bool ThumbnailDatabase::GetFaviconBitmapIDSizes( 646 favicon_base::FaviconID icon_id, 647 std::vector<FaviconBitmapIDSize>* bitmap_id_sizes) { 648 DCHECK(icon_id); 649 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 650 "SELECT id, width, height FROM favicon_bitmaps WHERE icon_id=?")); 651 statement.BindInt64(0, icon_id); 652 653 bool result = false; 654 while (statement.Step()) { 655 result = true; 656 if (!bitmap_id_sizes) 657 return result; 658 659 FaviconBitmapIDSize bitmap_id_size; 660 bitmap_id_size.bitmap_id = statement.ColumnInt64(0); 661 bitmap_id_size.pixel_size = gfx::Size(statement.ColumnInt(1), 662 statement.ColumnInt(2)); 663 bitmap_id_sizes->push_back(bitmap_id_size); 664 } 665 return result; 666 } 667 668 bool ThumbnailDatabase::GetFaviconBitmaps( 669 favicon_base::FaviconID icon_id, 670 std::vector<FaviconBitmap>* favicon_bitmaps) { 671 DCHECK(icon_id); 672 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 673 "SELECT id, last_updated, image_data, width, height FROM favicon_bitmaps " 674 "WHERE icon_id=?")); 675 statement.BindInt64(0, icon_id); 676 677 bool result = false; 678 while (statement.Step()) { 679 result = true; 680 if (!favicon_bitmaps) 681 return result; 682 683 FaviconBitmap favicon_bitmap; 684 favicon_bitmap.bitmap_id = statement.ColumnInt64(0); 685 favicon_bitmap.icon_id = icon_id; 686 favicon_bitmap.last_updated = 687 base::Time::FromInternalValue(statement.ColumnInt64(1)); 688 if (statement.ColumnByteLength(2) > 0) { 689 scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); 690 statement.ColumnBlobAsVector(2, &data->data()); 691 favicon_bitmap.bitmap_data = data; 692 } 693 favicon_bitmap.pixel_size = gfx::Size(statement.ColumnInt(3), 694 statement.ColumnInt(4)); 695 favicon_bitmaps->push_back(favicon_bitmap); 696 } 697 return result; 698 } 699 700 bool ThumbnailDatabase::GetFaviconBitmap( 701 FaviconBitmapID bitmap_id, 702 base::Time* last_updated, 703 scoped_refptr<base::RefCountedMemory>* png_icon_data, 704 gfx::Size* pixel_size) { 705 DCHECK(bitmap_id); 706 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 707 "SELECT last_updated, image_data, width, height FROM favicon_bitmaps " 708 "WHERE id=?")); 709 statement.BindInt64(0, bitmap_id); 710 711 if (!statement.Step()) 712 return false; 713 714 if (last_updated) 715 *last_updated = base::Time::FromInternalValue(statement.ColumnInt64(0)); 716 717 if (png_icon_data && statement.ColumnByteLength(1) > 0) { 718 scoped_refptr<base::RefCountedBytes> data(new base::RefCountedBytes()); 719 statement.ColumnBlobAsVector(1, &data->data()); 720 *png_icon_data = data; 721 } 722 723 if (pixel_size) { 724 *pixel_size = gfx::Size(statement.ColumnInt(2), 725 statement.ColumnInt(3)); 726 } 727 return true; 728 } 729 730 FaviconBitmapID ThumbnailDatabase::AddFaviconBitmap( 731 favicon_base::FaviconID icon_id, 732 const scoped_refptr<base::RefCountedMemory>& icon_data, 733 base::Time time, 734 const gfx::Size& pixel_size) { 735 DCHECK(icon_id); 736 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 737 "INSERT INTO favicon_bitmaps (icon_id, image_data, last_updated, width, " 738 "height) VALUES (?, ?, ?, ?, ?)")); 739 statement.BindInt64(0, icon_id); 740 if (icon_data.get() && icon_data->size()) { 741 statement.BindBlob(1, icon_data->front(), 742 static_cast<int>(icon_data->size())); 743 } else { 744 statement.BindNull(1); 745 } 746 statement.BindInt64(2, time.ToInternalValue()); 747 statement.BindInt(3, pixel_size.width()); 748 statement.BindInt(4, pixel_size.height()); 749 750 if (!statement.Run()) 751 return 0; 752 return db_.GetLastInsertRowId(); 753 } 754 755 bool ThumbnailDatabase::SetFaviconBitmap( 756 FaviconBitmapID bitmap_id, 757 scoped_refptr<base::RefCountedMemory> bitmap_data, 758 base::Time time) { 759 DCHECK(bitmap_id); 760 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 761 "UPDATE favicon_bitmaps SET image_data=?, last_updated=? WHERE id=?")); 762 if (bitmap_data.get() && bitmap_data->size()) { 763 statement.BindBlob(0, bitmap_data->front(), 764 static_cast<int>(bitmap_data->size())); 765 } else { 766 statement.BindNull(0); 767 } 768 statement.BindInt64(1, time.ToInternalValue()); 769 statement.BindInt64(2, bitmap_id); 770 771 return statement.Run(); 772 } 773 774 bool ThumbnailDatabase::SetFaviconBitmapLastUpdateTime( 775 FaviconBitmapID bitmap_id, 776 base::Time time) { 777 DCHECK(bitmap_id); 778 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 779 "UPDATE favicon_bitmaps SET last_updated=? WHERE id=?")); 780 statement.BindInt64(0, time.ToInternalValue()); 781 statement.BindInt64(1, bitmap_id); 782 return statement.Run(); 783 } 784 785 bool ThumbnailDatabase::DeleteFaviconBitmap(FaviconBitmapID bitmap_id) { 786 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 787 "DELETE FROM favicon_bitmaps WHERE id=?")); 788 statement.BindInt64(0, bitmap_id); 789 return statement.Run(); 790 } 791 792 bool ThumbnailDatabase::SetFaviconOutOfDate(favicon_base::FaviconID icon_id) { 793 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 794 "UPDATE favicon_bitmaps SET last_updated=? WHERE icon_id=?")); 795 statement.BindInt64(0, 0); 796 statement.BindInt64(1, icon_id); 797 798 return statement.Run(); 799 } 800 801 favicon_base::FaviconID ThumbnailDatabase::GetFaviconIDForFaviconURL( 802 const GURL& icon_url, 803 int required_icon_type, 804 favicon_base::IconType* icon_type) { 805 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 806 "SELECT id, icon_type FROM favicons WHERE url=? AND (icon_type & ? > 0) " 807 "ORDER BY icon_type DESC")); 808 statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url)); 809 statement.BindInt(1, required_icon_type); 810 811 if (!statement.Step()) 812 return 0; // not cached 813 814 if (icon_type) 815 *icon_type = static_cast<favicon_base::IconType>(statement.ColumnInt(1)); 816 return statement.ColumnInt64(0); 817 } 818 819 bool ThumbnailDatabase::GetFaviconHeader(favicon_base::FaviconID icon_id, 820 GURL* icon_url, 821 favicon_base::IconType* icon_type) { 822 DCHECK(icon_id); 823 824 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 825 "SELECT url, icon_type FROM favicons WHERE id=?")); 826 statement.BindInt64(0, icon_id); 827 828 if (!statement.Step()) 829 return false; // No entry for the id. 830 831 if (icon_url) 832 *icon_url = GURL(statement.ColumnString(0)); 833 if (icon_type) 834 *icon_type = static_cast<favicon_base::IconType>(statement.ColumnInt(1)); 835 836 return true; 837 } 838 839 favicon_base::FaviconID ThumbnailDatabase::AddFavicon( 840 const GURL& icon_url, 841 favicon_base::IconType icon_type) { 842 843 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 844 "INSERT INTO favicons (url, icon_type) VALUES (?, ?)")); 845 statement.BindString(0, URLDatabase::GURLToDatabaseURL(icon_url)); 846 statement.BindInt(1, icon_type); 847 848 if (!statement.Run()) 849 return 0; 850 return db_.GetLastInsertRowId(); 851 } 852 853 favicon_base::FaviconID ThumbnailDatabase::AddFavicon( 854 const GURL& icon_url, 855 favicon_base::IconType icon_type, 856 const scoped_refptr<base::RefCountedMemory>& icon_data, 857 base::Time time, 858 const gfx::Size& pixel_size) { 859 favicon_base::FaviconID icon_id = AddFavicon(icon_url, icon_type); 860 if (!icon_id || !AddFaviconBitmap(icon_id, icon_data, time, pixel_size)) 861 return 0; 862 863 return icon_id; 864 } 865 866 bool ThumbnailDatabase::DeleteFavicon(favicon_base::FaviconID id) { 867 sql::Statement statement; 868 statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, 869 "DELETE FROM favicons WHERE id = ?")); 870 statement.BindInt64(0, id); 871 if (!statement.Run()) 872 return false; 873 874 statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, 875 "DELETE FROM favicon_bitmaps WHERE icon_id = ?")); 876 statement.BindInt64(0, id); 877 return statement.Run(); 878 } 879 880 bool ThumbnailDatabase::GetIconMappingsForPageURL( 881 const GURL& page_url, 882 int required_icon_types, 883 std::vector<IconMapping>* filtered_mapping_data) { 884 std::vector<IconMapping> mapping_data; 885 if (!GetIconMappingsForPageURL(page_url, &mapping_data)) 886 return false; 887 888 bool result = false; 889 for (std::vector<IconMapping>::iterator m = mapping_data.begin(); 890 m != mapping_data.end(); ++m) { 891 if (m->icon_type & required_icon_types) { 892 result = true; 893 if (!filtered_mapping_data) 894 return result; 895 896 // Restrict icon type of subsequent matches to |m->icon_type|. 897 // |m->icon_type| is the largest IconType in |mapping_data| because 898 // |mapping_data| is sorted in descending order of IconType. 899 required_icon_types = m->icon_type; 900 901 filtered_mapping_data->push_back(*m); 902 } 903 } 904 return result; 905 } 906 907 bool ThumbnailDatabase::GetIconMappingsForPageURL( 908 const GURL& page_url, 909 std::vector<IconMapping>* mapping_data) { 910 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 911 "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, " 912 "favicons.url " 913 "FROM icon_mapping " 914 "INNER JOIN favicons " 915 "ON icon_mapping.icon_id = favicons.id " 916 "WHERE icon_mapping.page_url=? " 917 "ORDER BY favicons.icon_type DESC")); 918 statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); 919 920 bool result = false; 921 while (statement.Step()) { 922 result = true; 923 if (!mapping_data) 924 return result; 925 926 IconMapping icon_mapping; 927 FillIconMapping(statement, page_url, &icon_mapping); 928 mapping_data->push_back(icon_mapping); 929 } 930 return result; 931 } 932 933 IconMappingID ThumbnailDatabase::AddIconMapping( 934 const GURL& page_url, 935 favicon_base::FaviconID icon_id) { 936 const char kSql[] = 937 "INSERT INTO icon_mapping (page_url, icon_id) VALUES (?, ?)"; 938 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, kSql)); 939 statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); 940 statement.BindInt64(1, icon_id); 941 942 if (!statement.Run()) 943 return 0; 944 945 return db_.GetLastInsertRowId(); 946 } 947 948 bool ThumbnailDatabase::UpdateIconMapping(IconMappingID mapping_id, 949 favicon_base::FaviconID icon_id) { 950 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 951 "UPDATE icon_mapping SET icon_id=? WHERE id=?")); 952 statement.BindInt64(0, icon_id); 953 statement.BindInt64(1, mapping_id); 954 955 return statement.Run(); 956 } 957 958 bool ThumbnailDatabase::DeleteIconMappings(const GURL& page_url) { 959 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 960 "DELETE FROM icon_mapping WHERE page_url = ?")); 961 statement.BindString(0, URLDatabase::GURLToDatabaseURL(page_url)); 962 963 return statement.Run(); 964 } 965 966 bool ThumbnailDatabase::DeleteIconMapping(IconMappingID mapping_id) { 967 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 968 "DELETE FROM icon_mapping WHERE id=?")); 969 statement.BindInt64(0, mapping_id); 970 971 return statement.Run(); 972 } 973 974 bool ThumbnailDatabase::HasMappingFor(favicon_base::FaviconID id) { 975 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 976 "SELECT id FROM icon_mapping " 977 "WHERE icon_id=?")); 978 statement.BindInt64(0, id); 979 980 return statement.Step(); 981 } 982 983 bool ThumbnailDatabase::CloneIconMappings(const GURL& old_page_url, 984 const GURL& new_page_url) { 985 sql::Statement statement(db_.GetCachedStatement(SQL_FROM_HERE, 986 "SELECT icon_id FROM icon_mapping " 987 "WHERE page_url=?")); 988 if (!statement.is_valid()) 989 return false; 990 991 // Do nothing if there are existing bindings 992 statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url)); 993 if (statement.Step()) 994 return true; 995 996 statement.Assign(db_.GetCachedStatement(SQL_FROM_HERE, 997 "INSERT INTO icon_mapping (page_url, icon_id) " 998 "SELECT ?, icon_id FROM icon_mapping " 999 "WHERE page_url = ?")); 1000 1001 statement.BindString(0, URLDatabase::GURLToDatabaseURL(new_page_url)); 1002 statement.BindString(1, URLDatabase::GURLToDatabaseURL(old_page_url)); 1003 return statement.Run(); 1004 } 1005 1006 bool ThumbnailDatabase::InitIconMappingEnumerator( 1007 favicon_base::IconType type, 1008 IconMappingEnumerator* enumerator) { 1009 DCHECK(!enumerator->statement_.is_valid()); 1010 enumerator->statement_.Assign(db_.GetCachedStatement( 1011 SQL_FROM_HERE, 1012 "SELECT icon_mapping.id, icon_mapping.icon_id, favicons.icon_type, " 1013 "favicons.url, icon_mapping.page_url " 1014 "FROM icon_mapping JOIN favicons ON (" 1015 "icon_mapping.icon_id = favicons.id) " 1016 "WHERE favicons.icon_type = ?")); 1017 enumerator->statement_.BindInt(0, type); 1018 return enumerator->statement_.is_valid(); 1019 } 1020 1021 bool ThumbnailDatabase::RetainDataForPageUrls( 1022 const std::vector<GURL>& urls_to_keep) { 1023 sql::Transaction transaction(&db_); 1024 if (!transaction.Begin()) 1025 return false; 1026 1027 // temp.icon_id_mapping generates new icon ids as consecutive 1028 // integers starting from 1, and maps them to the old icon ids. 1029 { 1030 const char kIconMappingCreate[] = 1031 "CREATE TEMP TABLE icon_id_mapping " 1032 "(" 1033 "new_icon_id INTEGER PRIMARY KEY," 1034 "old_icon_id INTEGER NOT NULL UNIQUE" 1035 ")"; 1036 if (!db_.Execute(kIconMappingCreate)) 1037 return false; 1038 1039 // Insert the icon ids for retained urls, skipping duplicates. 1040 const char kIconMappingSql[] = 1041 "INSERT OR IGNORE INTO temp.icon_id_mapping (old_icon_id) " 1042 "SELECT icon_id FROM icon_mapping WHERE page_url = ?"; 1043 sql::Statement statement(db_.GetUniqueStatement(kIconMappingSql)); 1044 for (std::vector<GURL>::const_iterator 1045 i = urls_to_keep.begin(); i != urls_to_keep.end(); ++i) { 1046 statement.BindString(0, URLDatabase::GURLToDatabaseURL(*i)); 1047 if (!statement.Run()) 1048 return false; 1049 statement.Reset(true); 1050 } 1051 } 1052 1053 const char kRenameIconMappingTable[] = 1054 "ALTER TABLE icon_mapping RENAME TO old_icon_mapping"; 1055 const char kCopyIconMapping[] = 1056 "INSERT INTO icon_mapping (page_url, icon_id) " 1057 "SELECT old.page_url, mapping.new_icon_id " 1058 "FROM old_icon_mapping AS old " 1059 "JOIN temp.icon_id_mapping AS mapping " 1060 "ON (old.icon_id = mapping.old_icon_id)"; 1061 const char kDropOldIconMappingTable[] = "DROP TABLE old_icon_mapping"; 1062 1063 const char kRenameFaviconsTable[] = 1064 "ALTER TABLE favicons RENAME TO old_favicons"; 1065 const char kCopyFavicons[] = 1066 "INSERT INTO favicons (id, url, icon_type) " 1067 "SELECT mapping.new_icon_id, old.url, old.icon_type " 1068 "FROM old_favicons AS old " 1069 "JOIN temp.icon_id_mapping AS mapping " 1070 "ON (old.id = mapping.old_icon_id)"; 1071 const char kDropOldFaviconsTable[] = "DROP TABLE old_favicons"; 1072 1073 const char kRenameFaviconBitmapsTable[] = 1074 "ALTER TABLE favicon_bitmaps RENAME TO old_favicon_bitmaps"; 1075 const char kCopyFaviconBitmaps[] = 1076 "INSERT INTO favicon_bitmaps " 1077 " (icon_id, last_updated, image_data, width, height) " 1078 "SELECT mapping.new_icon_id, old.last_updated, " 1079 " old.image_data, old.width, old.height " 1080 "FROM old_favicon_bitmaps AS old " 1081 "JOIN temp.icon_id_mapping AS mapping " 1082 "ON (old.icon_id = mapping.old_icon_id)"; 1083 const char kDropOldFaviconBitmapsTable[] = 1084 "DROP TABLE old_favicon_bitmaps"; 1085 1086 // Rename existing tables to new location. 1087 if (!db_.Execute(kRenameIconMappingTable) || 1088 !db_.Execute(kRenameFaviconsTable) || 1089 !db_.Execute(kRenameFaviconBitmapsTable)) { 1090 return false; 1091 } 1092 1093 // Initialize the replacement tables. At this point the old indices 1094 // still exist (pointing to the old_* tables), so do not initialize 1095 // the indices. 1096 if (!InitTables(&db_)) 1097 return false; 1098 1099 // Copy all of the data over. 1100 if (!db_.Execute(kCopyIconMapping) || 1101 !db_.Execute(kCopyFavicons) || 1102 !db_.Execute(kCopyFaviconBitmaps)) { 1103 return false; 1104 } 1105 1106 // Drop the old_* tables, which also drops the indices. 1107 if (!db_.Execute(kDropOldIconMappingTable) || 1108 !db_.Execute(kDropOldFaviconsTable) || 1109 !db_.Execute(kDropOldFaviconBitmapsTable)) { 1110 return false; 1111 } 1112 1113 // Recreate the indices. 1114 // TODO(shess): UNIQUE indices could fail due to duplication. This 1115 // could happen in case of corruption. 1116 if (!InitIndices(&db_)) 1117 return false; 1118 1119 const char kIconMappingDrop[] = "DROP TABLE temp.icon_id_mapping"; 1120 if (!db_.Execute(kIconMappingDrop)) 1121 return false; 1122 1123 return transaction.Commit(); 1124 } 1125 1126 sql::InitStatus ThumbnailDatabase::OpenDatabase(sql::Connection* db, 1127 const base::FilePath& db_name) { 1128 size_t startup_kb = 0; 1129 int64 size_64; 1130 if (base::GetFileSize(db_name, &size_64)) 1131 startup_kb = static_cast<size_t>(size_64 / 1024); 1132 1133 db->set_histogram_tag("Thumbnail"); 1134 db->set_error_callback(base::Bind(&DatabaseErrorCallback, 1135 db, db_name, startup_kb, history_client_)); 1136 1137 // Thumbnails db now only stores favicons, so we don't need that big a page 1138 // size or cache. 1139 db->set_page_size(2048); 1140 db->set_cache_size(32); 1141 1142 // Run the database in exclusive mode. Nobody else should be accessing the 1143 // database while we're running, and this will give somewhat improved perf. 1144 db->set_exclusive_locking(); 1145 1146 if (!db->Open(db_name)) 1147 return sql::INIT_FAILURE; 1148 1149 return sql::INIT_OK; 1150 } 1151 1152 sql::InitStatus ThumbnailDatabase::InitImpl(const base::FilePath& db_name) { 1153 sql::InitStatus status = OpenDatabase(&db_, db_name); 1154 if (status != sql::INIT_OK) 1155 return status; 1156 1157 // Clear databases which are too old to process. 1158 DCHECK_LT(kDeprecatedVersionNumber, kCurrentVersionNumber); 1159 sql::MetaTable::RazeIfDeprecated(&db_, kDeprecatedVersionNumber); 1160 1161 // TODO(shess): Sqlite.Version.Thumbnail shows versions 22, 23, and 1162 // 25. Future versions are not destroyed because that could lead to 1163 // data loss if the profile is opened by a later channel, but 1164 // perhaps a heuristic like >kCurrentVersionNumber+3 could be used. 1165 1166 // Scope initialization in a transaction so we can't be partially initialized. 1167 sql::Transaction transaction(&db_); 1168 if (!transaction.Begin()) 1169 return sql::INIT_FAILURE; 1170 1171 // TODO(shess): Failing Begin() implies that something serious is 1172 // wrong with the database. Raze() may be in order. 1173 1174 #if defined(OS_MACOSX) 1175 // Exclude the thumbnails file from backups. 1176 base::mac::SetFileBackupExclusion(db_name); 1177 #endif 1178 1179 // thumbnails table has been obsolete for a long time, remove any 1180 // detrious. 1181 ignore_result(db_.Execute("DROP TABLE IF EXISTS thumbnails")); 1182 1183 // At some point, operations involving temporary tables weren't done 1184 // atomically and users have been stranded. Drop those tables and 1185 // move on. 1186 // TODO(shess): Prove it? Audit all cases and see if it's possible 1187 // that this implies non-atomic update, and should thus be handled 1188 // via the corruption handler. 1189 ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_favicons")); 1190 ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_favicon_bitmaps")); 1191 ignore_result(db_.Execute("DROP TABLE IF EXISTS temp_icon_mapping")); 1192 1193 // Create the tables. 1194 if (!meta_table_.Init(&db_, kCurrentVersionNumber, 1195 kCompatibleVersionNumber) || 1196 !InitTables(&db_) || 1197 !InitIndices(&db_)) { 1198 return sql::INIT_FAILURE; 1199 } 1200 1201 // Version check. We should not encounter a database too old for us to handle 1202 // in the wild, so we try to continue in that case. 1203 if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) { 1204 LOG(WARNING) << "Thumbnail database is too new."; 1205 return sql::INIT_TOO_NEW; 1206 } 1207 1208 int cur_version = meta_table_.GetVersionNumber(); 1209 1210 if (!db_.DoesColumnExist("favicons", "icon_type")) { 1211 LOG(ERROR) << "Raze because of missing favicon.icon_type"; 1212 RecordInvalidStructure(STRUCTURE_EVENT_VERSION4); 1213 1214 db_.RazeAndClose(); 1215 return sql::INIT_FAILURE; 1216 } 1217 1218 if (cur_version < 7 && !db_.DoesColumnExist("favicons", "sizes")) { 1219 LOG(ERROR) << "Raze because of missing favicon.sizes"; 1220 RecordInvalidStructure(STRUCTURE_EVENT_VERSION5); 1221 1222 db_.RazeAndClose(); 1223 return sql::INIT_FAILURE; 1224 } 1225 1226 if (cur_version == 5) { 1227 ++cur_version; 1228 if (!UpgradeToVersion6()) 1229 return CantUpgradeToVersion(cur_version); 1230 } 1231 1232 if (cur_version == 6) { 1233 ++cur_version; 1234 if (!UpgradeToVersion7()) 1235 return CantUpgradeToVersion(cur_version); 1236 } 1237 1238 LOG_IF(WARNING, cur_version < kCurrentVersionNumber) << 1239 "Thumbnail database version " << cur_version << " is too old to handle."; 1240 1241 // Initialization is complete. 1242 if (!transaction.Commit()) 1243 return sql::INIT_FAILURE; 1244 1245 // Raze the database if the structure of the favicons database is not what 1246 // it should be. This error cannot be detected via the SQL error code because 1247 // the error code for running SQL statements against a database with missing 1248 // columns is SQLITE_ERROR which is not unique enough to act upon. 1249 // TODO(pkotwicz): Revisit this in M27 and see if the razing can be removed. 1250 // (crbug.com/166453) 1251 if (IsFaviconDBStructureIncorrect()) { 1252 LOG(ERROR) << "Raze because of invalid favicon db structure."; 1253 RecordInvalidStructure(STRUCTURE_EVENT_FAVICON); 1254 1255 db_.RazeAndClose(); 1256 return sql::INIT_FAILURE; 1257 } 1258 1259 return sql::INIT_OK; 1260 } 1261 1262 sql::InitStatus ThumbnailDatabase::CantUpgradeToVersion(int cur_version) { 1263 LOG(WARNING) << "Unable to update to thumbnail database to version " << 1264 cur_version << "."; 1265 db_.Close(); 1266 return sql::INIT_FAILURE; 1267 } 1268 1269 bool ThumbnailDatabase::UpgradeToVersion6() { 1270 // Move bitmap data from favicons to favicon_bitmaps. 1271 bool success = 1272 db_.Execute("INSERT INTO favicon_bitmaps (icon_id, last_updated, " 1273 "image_data, width, height)" 1274 "SELECT id, last_updated, image_data, 0, 0 FROM favicons") && 1275 db_.Execute("CREATE TABLE temp_favicons (" 1276 "id INTEGER PRIMARY KEY," 1277 "url LONGVARCHAR NOT NULL," 1278 "icon_type INTEGER DEFAULT 1," 1279 // default icon_type FAVICON to be consistent with 1280 // past migration. 1281 "sizes LONGVARCHAR)") && 1282 db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) " 1283 "SELECT id, url, icon_type FROM favicons") && 1284 db_.Execute("DROP TABLE favicons") && 1285 db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons"); 1286 // NOTE(shess): v7 will re-create the index. 1287 if (!success) 1288 return false; 1289 1290 meta_table_.SetVersionNumber(6); 1291 meta_table_.SetCompatibleVersionNumber(std::min(6, kCompatibleVersionNumber)); 1292 return true; 1293 } 1294 1295 bool ThumbnailDatabase::UpgradeToVersion7() { 1296 // Sizes column was never used, remove it. 1297 bool success = 1298 db_.Execute("CREATE TABLE temp_favicons (" 1299 "id INTEGER PRIMARY KEY," 1300 "url LONGVARCHAR NOT NULL," 1301 // default icon_type FAVICON to be consistent with 1302 // past migration. 1303 "icon_type INTEGER DEFAULT 1)") && 1304 db_.Execute("INSERT INTO temp_favicons (id, url, icon_type) " 1305 "SELECT id, url, icon_type FROM favicons") && 1306 db_.Execute("DROP TABLE favicons") && 1307 db_.Execute("ALTER TABLE temp_favicons RENAME TO favicons") && 1308 db_.Execute("CREATE INDEX IF NOT EXISTS favicons_url ON favicons(url)"); 1309 1310 if (!success) 1311 return false; 1312 1313 meta_table_.SetVersionNumber(7); 1314 meta_table_.SetCompatibleVersionNumber(std::min(7, kCompatibleVersionNumber)); 1315 return true; 1316 } 1317 1318 bool ThumbnailDatabase::IsFaviconDBStructureIncorrect() { 1319 return !db_.IsSQLValid("SELECT id, url, icon_type FROM favicons"); 1320 } 1321 1322 } // namespace history 1323