Home | History | Annotate | Download | only in history
      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/top_sites_database.h"
      6 
      7 #include "base/files/file_util.h"
      8 #include "base/memory/ref_counted.h"
      9 #include "base/metrics/histogram.h"
     10 #include "base/strings/string_split.h"
     11 #include "base/strings/string_util.h"
     12 #include "chrome/browser/history/top_sites.h"
     13 #include "components/history/core/browser/history_types.h"
     14 #include "components/history/core/common/thumbnail_score.h"
     15 #include "sql/connection.h"
     16 #include "sql/recovery.h"
     17 #include "sql/statement.h"
     18 #include "sql/transaction.h"
     19 #include "third_party/sqlite/sqlite3.h"
     20 
     21 // Description of database table:
     22 //
     23 // thumbnails
     24 //   url              URL of the sites for which we have a thumbnail.
     25 //   url_rank         Index of the URL in that thumbnail, 0-based. The thumbnail
     26 //                    with the highest rank will be the next one evicted. Forced
     27 //                    thumbnails have a rank of -1.
     28 //   title            The title to display under that thumbnail.
     29 //   redirects        A space separated list of URLs that are known to redirect
     30 //                    to this url.
     31 //   boring_score     How "boring" that thumbnail is. See ThumbnailScore.
     32 //   good_clipping    True if the thumbnail was clipped from the bottom, keeping
     33 //                    the entire width of the window. See ThumbnailScore.
     34 //   at_top           True if the thumbnail was captured at the top of the
     35 //                    website.
     36 //   last_updated     The time at which this thumbnail was last updated.
     37 //   load_completed   True if the thumbnail was captured after the page load was
     38 //                    completed.
     39 //   last_forced      If this is a forced thumbnail, records the last time it
     40 //                    was forced. If it's not a forced thumbnail, 0.
     41 
     42 namespace {
     43 
     44 // For this database, schema migrations are deprecated after two
     45 // years.  This means that the oldest non-deprecated version should be
     46 // two years old or greater (thus the migrations to get there are
     47 // older).  Databases containing deprecated versions will be cleared
     48 // at startup.  Since this database is a cache, losing old data is not
     49 // fatal (in fact, very old data may be expired immediately at startup
     50 // anyhow).
     51 
     52 // Version 3: b6d6a783/r231648 by beaudoin (at) chromium.org on 2013-10-29
     53 // Version 2: eb0b24e6/r87284 by satorux (at) chromium.org on 2011-05-31
     54 // Version 1: 809cc4d8/r64072 by sky (at) chromium.org on 2010-10-27 (deprecated)
     55 
     56 // NOTE(shess): When changing the version, add a new golden file for
     57 // the new version and a test to verify that Init() works with it.
     58 // NOTE(shess): RecoverDatabaseOrRaze() depends on the specific
     59 // version number.  The code is subtle and in development, contact me
     60 // if the necessary changes are not obvious.
     61 static const int kVersionNumber = 3;
     62 static const int kDeprecatedVersionNumber = 1;  // and earlier.
     63 
     64 bool InitTables(sql::Connection* db) {
     65   const char kThumbnailsSql[] =
     66       "CREATE TABLE IF NOT EXISTS thumbnails ("
     67       "url LONGVARCHAR PRIMARY KEY,"
     68       "url_rank INTEGER,"
     69       "title LONGVARCHAR,"
     70       "thumbnail BLOB,"
     71       "redirects LONGVARCHAR,"
     72       "boring_score DOUBLE DEFAULT 1.0,"
     73       "good_clipping INTEGER DEFAULT 0,"
     74       "at_top INTEGER DEFAULT 0,"
     75       "last_updated INTEGER DEFAULT 0,"
     76       "load_completed INTEGER DEFAULT 0,"
     77       "last_forced INTEGER DEFAULT 0)";
     78   return db->Execute(kThumbnailsSql);
     79 }
     80 
     81 // Encodes redirects into a string.
     82 std::string GetRedirects(const history::MostVisitedURL& url) {
     83   std::vector<std::string> redirects;
     84   for (size_t i = 0; i < url.redirects.size(); i++)
     85     redirects.push_back(url.redirects[i].spec());
     86   return JoinString(redirects, ' ');
     87 }
     88 
     89 // Decodes redirects from a string and sets them for the url.
     90 void SetRedirects(const std::string& redirects, history::MostVisitedURL* url) {
     91   std::vector<std::string> redirects_vector;
     92   base::SplitStringAlongWhitespace(redirects, &redirects_vector);
     93   for (size_t i = 0; i < redirects_vector.size(); ++i) {
     94     GURL redirects_url(redirects_vector[i]);
     95     if (redirects_url.is_valid())
     96       url->redirects.push_back(redirects_url);
     97   }
     98 }
     99 
    100 // Track various failure (and success) cases in recovery code.
    101 //
    102 // TODO(shess): The recovery code is complete, but by nature runs in challenging
    103 // circumstances, so initially the default error response is to leave the
    104 // existing database in place.  This histogram is intended to expose the
    105 // failures seen in the fleet.  Frequent failure cases can be explored more
    106 // deeply to see if the complexity to fix them is warranted.  Infrequent failure
    107 // cases can be resolved by marking the database unrecoverable (which will
    108 // delete the data).
    109 //
    110 // Based on the thumbnail_database.cc recovery code, FAILED_SCOPER should
    111 // dominate, followed distantly by FAILED_META, with few or no other failures.
    112 enum RecoveryEventType {
    113   // Database successfully recovered.
    114   RECOVERY_EVENT_RECOVERED = 0,
    115 
    116   // Database successfully deprecated.
    117   RECOVERY_EVENT_DEPRECATED,
    118 
    119   // Sqlite.RecoveryEvent can usually be used to get more detail about the
    120   // specific failure (see sql/recovery.cc).
    121   RECOVERY_EVENT_FAILED_SCOPER,
    122   RECOVERY_EVENT_FAILED_META_VERSION,
    123   RECOVERY_EVENT_FAILED_META_WRONG_VERSION,
    124   RECOVERY_EVENT_FAILED_META_INIT,
    125   RECOVERY_EVENT_FAILED_SCHEMA_INIT,
    126   RECOVERY_EVENT_FAILED_AUTORECOVER_THUMBNAILS,
    127   RECOVERY_EVENT_FAILED_COMMIT,
    128 
    129   // Track invariants resolved by FixThumbnailsTable().
    130   RECOVERY_EVENT_INVARIANT_RANK,
    131   RECOVERY_EVENT_INVARIANT_REDIRECT,
    132   RECOVERY_EVENT_INVARIANT_CONTIGUOUS,
    133 
    134   // Always keep this at the end.
    135   RECOVERY_EVENT_MAX,
    136 };
    137 
    138 void RecordRecoveryEvent(RecoveryEventType recovery_event) {
    139   UMA_HISTOGRAM_ENUMERATION("History.TopSitesRecovery",
    140                             recovery_event, RECOVERY_EVENT_MAX);
    141 }
    142 
    143 // Most corruption comes down to atomic updates between pages being broken
    144 // somehow.  This can result in either missing data, or overlapping data,
    145 // depending on the operation broken.  This table has large rows, which will use
    146 // overflow pages, so it is possible (though unlikely) that a chain could fit
    147 // together and yield a row with errors.
    148 void FixThumbnailsTable(sql::Connection* db) {
    149   // Enforce invariant separating forced and non-forced thumbnails.
    150   const char kFixRankSql[] =
    151       "DELETE FROM thumbnails "
    152       "WHERE (url_rank = -1 AND last_forced = 0) "
    153       "OR (url_rank <> -1 AND last_forced <> 0)";
    154   ignore_result(db->Execute(kFixRankSql));
    155   if (db->GetLastChangeCount() > 0)
    156     RecordRecoveryEvent(RECOVERY_EVENT_INVARIANT_RANK);
    157 
    158   // Enforce invariant that url is in its own redirects.
    159   const char kFixRedirectsSql[] =
    160       "DELETE FROM thumbnails "
    161       "WHERE url <> substr(redirects, -length(url), length(url))";
    162   ignore_result(db->Execute(kFixRedirectsSql));
    163   if (db->GetLastChangeCount() > 0)
    164     RecordRecoveryEvent(RECOVERY_EVENT_INVARIANT_REDIRECT);
    165 
    166   // Enforce invariant that url_rank>=0 forms a contiguous series.
    167   // TODO(shess): I have not found an UPDATE+SUBSELECT method of managing this.
    168   // It can be done with a temporary table and a subselect, but doing it
    169   // manually is easier to follow.  Another option would be to somehow integrate
    170   // the renumbering into the table recovery code.
    171   const char kByRankSql[] =
    172       "SELECT url_rank, rowid FROM thumbnails WHERE url_rank <> -1 "
    173       "ORDER BY url_rank";
    174   sql::Statement select_statement(db->GetUniqueStatement(kByRankSql));
    175 
    176   const char kAdjustRankSql[] =
    177       "UPDATE thumbnails SET url_rank = ? WHERE rowid = ?";
    178   sql::Statement update_statement(db->GetUniqueStatement(kAdjustRankSql));
    179 
    180   // Update any rows where |next_rank| doesn't match |url_rank|.
    181   int next_rank = 0;
    182   bool adjusted = false;
    183   while (select_statement.Step()) {
    184     const int url_rank = select_statement.ColumnInt(0);
    185     if (url_rank != next_rank) {
    186       adjusted = true;
    187       update_statement.Reset(true);
    188       update_statement.BindInt(0, next_rank);
    189       update_statement.BindInt64(1, select_statement.ColumnInt64(1));
    190       update_statement.Run();
    191     }
    192     ++next_rank;
    193   }
    194   if (adjusted)
    195     RecordRecoveryEvent(RECOVERY_EVENT_INVARIANT_CONTIGUOUS);
    196 }
    197 
    198 // Recover the database to the extent possible, razing it if recovery is not
    199 // possible.
    200 void RecoverDatabaseOrRaze(sql::Connection* db, const base::FilePath& db_path) {
    201   // NOTE(shess): If the version changes, review this code.
    202   DCHECK_EQ(3, kVersionNumber);
    203 
    204   // It is almost certain that some operation against |db| will fail, prevent
    205   // reentry.
    206   db->reset_error_callback();
    207 
    208   // For generating histogram stats.
    209   size_t thumbnails_recovered = 0;
    210   int64 original_size = 0;
    211   base::GetFileSize(db_path, &original_size);
    212 
    213   scoped_ptr<sql::Recovery> recovery = sql::Recovery::Begin(db, db_path);
    214   if (!recovery) {
    215     RecordRecoveryEvent(RECOVERY_EVENT_FAILED_SCOPER);
    216     return;
    217   }
    218 
    219   // Setup the meta recovery table and fetch the version number from the corrupt
    220   // database.
    221   int version = 0;
    222   if (!recovery->SetupMeta() || !recovery->GetMetaVersionNumber(&version)) {
    223     // TODO(shess): Prior histograms indicate all failures are in creating the
    224     // recover virtual table for corrupt.meta.  The table may not exist, or the
    225     // database may be too far gone.  Either way, unclear how to resolve.
    226     sql::Recovery::Rollback(recovery.Pass());
    227     RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_VERSION);
    228     return;
    229   }
    230 
    231   // This code runs in a context which may be able to read version information
    232   // that the regular deprecation path cannot.  The effect of this code will be
    233   // to raze the database.
    234   if (version <= kDeprecatedVersionNumber) {
    235     sql::Recovery::Unrecoverable(recovery.Pass());
    236     RecordRecoveryEvent(RECOVERY_EVENT_DEPRECATED);
    237     return;
    238   }
    239 
    240   // TODO(shess): Earlier versions have been deprecated, later versions should
    241   // be impossible.  Unrecoverable() seems like a feasible response if this is
    242   // infrequent enough.
    243   if (version != 2 && version != 3) {
    244     RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_WRONG_VERSION);
    245     sql::Recovery::Rollback(recovery.Pass());
    246     return;
    247   }
    248 
    249   // Both v2 and v3 recover to current schema version.
    250   sql::MetaTable recover_meta_table;
    251   if (!recover_meta_table.Init(recovery->db(), kVersionNumber,
    252                                kVersionNumber)) {
    253     sql::Recovery::Rollback(recovery.Pass());
    254     RecordRecoveryEvent(RECOVERY_EVENT_FAILED_META_INIT);
    255     return;
    256   }
    257 
    258   // Create a fresh version of the schema.  The recovery code uses
    259   // conflict-resolution to handle duplicates, so any indices are necessary.
    260   if (!InitTables(recovery->db())) {
    261     // TODO(shess): Unable to create the new schema in the new database.  The
    262     // new database should be a temporary file, so being unable to work with it
    263     // is pretty unclear.
    264     //
    265     // What are the potential responses, even?  The recovery database could be
    266     // opened as in-memory.  If the temp database had a filesystem problem and
    267     // the temp filesystem differs from the main database, then that could fix
    268     // it.
    269     sql::Recovery::Rollback(recovery.Pass());
    270     RecordRecoveryEvent(RECOVERY_EVENT_FAILED_SCHEMA_INIT);
    271     return;
    272   }
    273 
    274   // The |1| is because v2 [thumbnails] has one less column than v3 did.  In the
    275   // v2 case the column will get default values.
    276   if (!recovery->AutoRecoverTable("thumbnails", 1, &thumbnails_recovered)) {
    277     sql::Recovery::Rollback(recovery.Pass());
    278     RecordRecoveryEvent(RECOVERY_EVENT_FAILED_AUTORECOVER_THUMBNAILS);
    279     return;
    280   }
    281 
    282   // TODO(shess): Inline this?
    283   FixThumbnailsTable(recovery->db());
    284 
    285   if (!sql::Recovery::Recovered(recovery.Pass())) {
    286     // TODO(shess): Very unclear what this failure would actually mean, and what
    287     // should be done.  Add histograms to Recovered() implementation to get some
    288     // insight.
    289     RecordRecoveryEvent(RECOVERY_EVENT_FAILED_COMMIT);
    290     return;
    291   }
    292 
    293   // Track the size of the recovered database relative to the size of the input
    294   // database.  The size should almost always be smaller, unless the input
    295   // database was empty to start with.  If the percentage results are very low,
    296   // something is awry.
    297   int64 final_size = 0;
    298   if (original_size > 0 &&
    299       base::GetFileSize(db_path, &final_size) &&
    300       final_size > 0) {
    301     UMA_HISTOGRAM_PERCENTAGE("History.TopSitesRecoveredPercentage",
    302                              final_size * 100 / original_size);
    303   }
    304 
    305   // Using 10,000 because these cases mostly care about "none recovered" and
    306   // "lots recovered".  More than 10,000 rows recovered probably means there's
    307   // something wrong with the profile.
    308   UMA_HISTOGRAM_COUNTS_10000("History.TopSitesRecoveredRowsThumbnails",
    309                              thumbnails_recovered);
    310 
    311   RecordRecoveryEvent(RECOVERY_EVENT_RECOVERED);
    312 }
    313 
    314 void DatabaseErrorCallback(sql::Connection* db,
    315                            const base::FilePath& db_path,
    316                            int extended_error,
    317                            sql::Statement* stmt) {
    318   // TODO(shess): Assert that this is running on a safe thread.  AFAICT, should
    319   // be the history thread, but at this level I can't see how to reach that.
    320 
    321   // Attempt to recover corrupt databases.
    322   int error = (extended_error & 0xFF);
    323   if (error == SQLITE_CORRUPT ||
    324       error == SQLITE_CANTOPEN ||
    325       error == SQLITE_NOTADB) {
    326     RecoverDatabaseOrRaze(db, db_path);
    327   }
    328 
    329   // TODO(shess): This database's error histograms look like:
    330   // 84% SQLITE_CORRUPT, SQLITE_CANTOPEN, SQLITE_NOTADB
    331   //  7% SQLITE_ERROR
    332   //  6% SQLITE_IOERR variants
    333   //  2% SQLITE_READONLY
    334   // .4% SQLITE_FULL
    335   // nominal SQLITE_TOBIG, SQLITE_AUTH, and SQLITE_BUSY.  In the case of
    336   // thumbnail_database.cc, as soon as the recovery code landed, SQLITE_IOERR
    337   // shot to leadership.  If the I/O error is system-level, there is probably no
    338   // hope, but if it is restricted to something about the database file, it is
    339   // possible that the recovery code could be brought to bear.  In fact, it is
    340   // possible that running recovery would be a reasonable default when errors
    341   // are seen.
    342 
    343   // The default handling is to assert on debug and to ignore on release.
    344   if (!sql::Connection::ShouldIgnoreSqliteError(extended_error))
    345     DLOG(FATAL) << db->GetErrorMessage();
    346 }
    347 
    348 }  // namespace
    349 
    350 namespace history {
    351 
    352 // static
    353 const int TopSitesDatabase::kRankOfForcedURL = -1;
    354 
    355 // static
    356 const int TopSitesDatabase::kRankOfNonExistingURL = -2;
    357 
    358 TopSitesDatabase::TopSitesDatabase() {
    359 }
    360 
    361 TopSitesDatabase::~TopSitesDatabase() {
    362 }
    363 
    364 bool TopSitesDatabase::Init(const base::FilePath& db_name) {
    365   // Retry failed InitImpl() in case the recovery system fixed things.
    366   // TODO(shess): Instrument to figure out if there are any persistent failure
    367   // cases which do not resolve themselves.
    368   const size_t kAttempts = 2;
    369 
    370   for (size_t i = 0; i < kAttempts; ++i) {
    371     if (InitImpl(db_name))
    372       return true;
    373 
    374     meta_table_.Reset();
    375     db_.reset();
    376   }
    377   return false;
    378 }
    379 
    380 bool TopSitesDatabase::InitImpl(const base::FilePath& db_name) {
    381   const bool file_existed = base::PathExists(db_name);
    382 
    383   db_.reset(CreateDB(db_name));
    384   if (!db_)
    385     return false;
    386 
    387   // An older version had data with no meta table.  Deprecate by razing.
    388   // TODO(shess): Just have RazeIfDeprecated() handle this case.
    389   const bool does_meta_exist = sql::MetaTable::DoesTableExist(db_.get());
    390   if (!does_meta_exist && file_existed) {
    391     if (!db_->Raze())
    392       return false;
    393   }
    394 
    395   // Clear databases which are too old to process.
    396   DCHECK_LT(kDeprecatedVersionNumber, kVersionNumber);
    397   sql::MetaTable::RazeIfDeprecated(db_.get(), kDeprecatedVersionNumber);
    398 
    399   // Scope initialization in a transaction so we can't be partially
    400   // initialized.
    401   sql::Transaction transaction(db_.get());
    402   // TODO(shess): Failure to open transaction is bad, address it.
    403   if (!transaction.Begin())
    404     return false;
    405 
    406   if (!meta_table_.Init(db_.get(), kVersionNumber, kVersionNumber))
    407     return false;
    408 
    409   if (!InitTables(db_.get()))
    410     return false;
    411 
    412   if (meta_table_.GetVersionNumber() == 2) {
    413     if (!UpgradeToVersion3()) {
    414       LOG(WARNING) << "Unable to upgrade top sites database to version 3.";
    415       return false;
    416     }
    417   }
    418 
    419   // Version check.
    420   if (meta_table_.GetVersionNumber() != kVersionNumber)
    421     return false;
    422 
    423   // Initialization is complete.
    424   if (!transaction.Commit())
    425     return false;
    426 
    427   return true;
    428 }
    429 
    430 bool TopSitesDatabase::UpgradeToVersion3() {
    431   // Add 'last_forced' column.
    432   if (!db_->Execute(
    433           "ALTER TABLE thumbnails ADD last_forced INTEGER DEFAULT 0")) {
    434     NOTREACHED();
    435     return false;
    436   }
    437   meta_table_.SetVersionNumber(3);
    438   return true;
    439 }
    440 
    441 void TopSitesDatabase::GetPageThumbnails(MostVisitedURLList* urls,
    442                                          URLToImagesMap* thumbnails) {
    443   sql::Statement statement(db_->GetCachedStatement(
    444       SQL_FROM_HERE,
    445       "SELECT url, url_rank, title, thumbnail, redirects, "
    446       "boring_score, good_clipping, at_top, last_updated, load_completed, "
    447       "last_forced FROM thumbnails ORDER BY url_rank, last_forced"));
    448 
    449   if (!statement.is_valid()) {
    450     LOG(WARNING) << db_->GetErrorMessage();
    451     return;
    452   }
    453 
    454   urls->clear();
    455   thumbnails->clear();
    456 
    457   while (statement.Step()) {
    458     // Results are sorted by url_rank. For forced thumbnails with url_rank = -1,
    459     // thumbnails are sorted by last_forced.
    460     MostVisitedURL url;
    461     GURL gurl(statement.ColumnString(0));
    462     url.url = gurl;
    463     url.title = statement.ColumnString16(2);
    464     url.last_forced_time =
    465         base::Time::FromInternalValue(statement.ColumnInt64(10));
    466     std::string redirects = statement.ColumnString(4);
    467     SetRedirects(redirects, &url);
    468     urls->push_back(url);
    469 
    470     std::vector<unsigned char> data;
    471     statement.ColumnBlobAsVector(3, &data);
    472     Images thumbnail;
    473     if (!data.empty())
    474       thumbnail.thumbnail = base::RefCountedBytes::TakeVector(&data);
    475     thumbnail.thumbnail_score.boring_score = statement.ColumnDouble(5);
    476     thumbnail.thumbnail_score.good_clipping = statement.ColumnBool(6);
    477     thumbnail.thumbnail_score.at_top = statement.ColumnBool(7);
    478     thumbnail.thumbnail_score.time_at_snapshot =
    479         base::Time::FromInternalValue(statement.ColumnInt64(8));
    480     thumbnail.thumbnail_score.load_completed = statement.ColumnBool(9);
    481     (*thumbnails)[gurl] = thumbnail;
    482   }
    483 }
    484 
    485 void TopSitesDatabase::SetPageThumbnail(const MostVisitedURL& url,
    486                                         int new_rank,
    487                                         const Images& thumbnail) {
    488   sql::Transaction transaction(db_.get());
    489   transaction.Begin();
    490 
    491   int rank = GetURLRank(url);
    492   if (rank == kRankOfNonExistingURL) {
    493     AddPageThumbnail(url, new_rank, thumbnail);
    494   } else {
    495     UpdatePageRankNoTransaction(url, new_rank);
    496     UpdatePageThumbnail(url, thumbnail);
    497   }
    498 
    499   transaction.Commit();
    500 }
    501 
    502 bool TopSitesDatabase::UpdatePageThumbnail(
    503     const MostVisitedURL& url, const Images& thumbnail) {
    504   sql::Statement statement(db_->GetCachedStatement(
    505       SQL_FROM_HERE,
    506       "UPDATE thumbnails SET "
    507       "title = ?, thumbnail = ?, redirects = ?, "
    508       "boring_score = ?, good_clipping = ?, at_top = ?, last_updated = ?, "
    509       "load_completed = ?, last_forced = ?"
    510       "WHERE url = ? "));
    511   statement.BindString16(0, url.title);
    512   if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) {
    513     statement.BindBlob(1, thumbnail.thumbnail->front(),
    514                        static_cast<int>(thumbnail.thumbnail->size()));
    515   }
    516   statement.BindString(2, GetRedirects(url));
    517   const ThumbnailScore& score = thumbnail.thumbnail_score;
    518   statement.BindDouble(3, score.boring_score);
    519   statement.BindBool(4, score.good_clipping);
    520   statement.BindBool(5, score.at_top);
    521   statement.BindInt64(6, score.time_at_snapshot.ToInternalValue());
    522   statement.BindBool(7, score.load_completed);
    523   statement.BindInt64(8, url.last_forced_time.ToInternalValue());
    524   statement.BindString(9, url.url.spec());
    525 
    526   return statement.Run();
    527 }
    528 
    529 void TopSitesDatabase::AddPageThumbnail(const MostVisitedURL& url,
    530                                         int new_rank,
    531                                         const Images& thumbnail) {
    532   sql::Statement statement(db_->GetCachedStatement(
    533       SQL_FROM_HERE,
    534       "INSERT OR REPLACE INTO thumbnails "
    535       "(url, url_rank, title, thumbnail, redirects, "
    536       "boring_score, good_clipping, at_top, last_updated, load_completed, "
    537       "last_forced) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
    538   statement.BindString(0, url.url.spec());
    539   statement.BindInt(1, kRankOfForcedURL);  // Fist make it a forced thumbnail.
    540   statement.BindString16(2, url.title);
    541   if (thumbnail.thumbnail.get() && thumbnail.thumbnail->front()) {
    542     statement.BindBlob(3, thumbnail.thumbnail->front(),
    543                        static_cast<int>(thumbnail.thumbnail->size()));
    544   }
    545   statement.BindString(4, GetRedirects(url));
    546   const ThumbnailScore& score = thumbnail.thumbnail_score;
    547   statement.BindDouble(5, score.boring_score);
    548   statement.BindBool(6, score.good_clipping);
    549   statement.BindBool(7, score.at_top);
    550   statement.BindInt64(8, score.time_at_snapshot.ToInternalValue());
    551   statement.BindBool(9, score.load_completed);
    552   int64 last_forced = url.last_forced_time.ToInternalValue();
    553   DCHECK((last_forced == 0) == (new_rank != kRankOfForcedURL))
    554       << "Thumbnail without a forced time stamp has a forced rank, or the "
    555       << "opposite.";
    556   statement.BindInt64(10, last_forced);
    557   if (!statement.Run())
    558     return;
    559 
    560   // Update rank if this is not a forced thumbnail.
    561   if (new_rank != kRankOfForcedURL)
    562     UpdatePageRankNoTransaction(url, new_rank);
    563 }
    564 
    565 void TopSitesDatabase::UpdatePageRank(const MostVisitedURL& url,
    566                                       int new_rank) {
    567   DCHECK((url.last_forced_time.ToInternalValue() == 0) ==
    568          (new_rank != kRankOfForcedURL))
    569       << "Thumbnail without a forced time stamp has a forced rank, or the "
    570       << "opposite.";
    571   sql::Transaction transaction(db_.get());
    572   transaction.Begin();
    573   UpdatePageRankNoTransaction(url, new_rank);
    574   transaction.Commit();
    575 }
    576 
    577 // Caller should have a transaction open.
    578 void TopSitesDatabase::UpdatePageRankNoTransaction(
    579     const MostVisitedURL& url, int new_rank) {
    580   DCHECK_GT(db_->transaction_nesting(), 0);
    581   DCHECK((url.last_forced_time.is_null()) == (new_rank != kRankOfForcedURL))
    582       << "Thumbnail without a forced time stamp has a forced rank, or the "
    583       << "opposite.";
    584 
    585   int prev_rank = GetURLRank(url);
    586   if (prev_rank == kRankOfNonExistingURL) {
    587     LOG(WARNING) << "Updating rank of an unknown URL: " << url.url.spec();
    588     return;
    589   }
    590 
    591   // Shift the ranks.
    592   if (prev_rank > new_rank) {
    593     if (new_rank == kRankOfForcedURL) {
    594       // From non-forced to forced, shift down.
    595       // Example: 2 -> -1
    596       // -1, -1, -1, 0, 1, [2 -> -1], [3 -> 2], [4 -> 3]
    597       sql::Statement shift_statement(db_->GetCachedStatement(
    598           SQL_FROM_HERE,
    599           "UPDATE thumbnails "
    600           "SET url_rank = url_rank - 1 "
    601           "WHERE url_rank > ?"));
    602       shift_statement.BindInt(0, prev_rank);
    603       shift_statement.Run();
    604     } else {
    605       // From non-forced to non-forced, shift up.
    606       // Example: 3 -> 1
    607       // -1, -1, -1, 0, [1 -> 2], [2 -> 3], [3 -> 1], 4
    608       sql::Statement shift_statement(db_->GetCachedStatement(
    609           SQL_FROM_HERE,
    610           "UPDATE thumbnails "
    611           "SET url_rank = url_rank + 1 "
    612           "WHERE url_rank >= ? AND url_rank < ?"));
    613       shift_statement.BindInt(0, new_rank);
    614       shift_statement.BindInt(1, prev_rank);
    615       shift_statement.Run();
    616     }
    617   } else if (prev_rank < new_rank) {
    618     if (prev_rank == kRankOfForcedURL) {
    619       // From non-forced to forced, shift up.
    620       // Example: -1 -> 2
    621       // -1, [-1 -> 2], -1, 0, 1, [2 -> 3], [3 -> 4], [4 -> 5]
    622       sql::Statement shift_statement(db_->GetCachedStatement(
    623           SQL_FROM_HERE,
    624           "UPDATE thumbnails "
    625           "SET url_rank = url_rank + 1 "
    626           "WHERE url_rank >= ?"));
    627       shift_statement.BindInt(0, new_rank);
    628       shift_statement.Run();
    629     } else {
    630       // From non-forced to non-forced, shift down.
    631       // Example: 1 -> 3.
    632       // -1, -1, -1, 0, [1 -> 3], [2 -> 1], [3 -> 2], 4
    633       sql::Statement shift_statement(db_->GetCachedStatement(
    634           SQL_FROM_HERE,
    635           "UPDATE thumbnails "
    636           "SET url_rank = url_rank - 1 "
    637           "WHERE url_rank > ? AND url_rank <= ?"));
    638       shift_statement.BindInt(0, prev_rank);
    639       shift_statement.BindInt(1, new_rank);
    640       shift_statement.Run();
    641     }
    642   }
    643 
    644   // Set the url's rank and last_forced, since the latter changes when a URL
    645   // goes from forced to non-forced and vice-versa.
    646   sql::Statement set_statement(db_->GetCachedStatement(
    647       SQL_FROM_HERE,
    648       "UPDATE thumbnails "
    649       "SET url_rank = ?, last_forced = ? "
    650       "WHERE url == ?"));
    651   set_statement.BindInt(0, new_rank);
    652   set_statement.BindInt64(1, url.last_forced_time.ToInternalValue());
    653   set_statement.BindString(2, url.url.spec());
    654   set_statement.Run();
    655 }
    656 
    657 bool TopSitesDatabase::GetPageThumbnail(const GURL& url,
    658                                         Images* thumbnail) {
    659   sql::Statement statement(db_->GetCachedStatement(
    660       SQL_FROM_HERE,
    661       "SELECT thumbnail, boring_score, good_clipping, at_top, last_updated "
    662       "FROM thumbnails WHERE url=?"));
    663   statement.BindString(0, url.spec());
    664   if (!statement.Step())
    665     return false;
    666 
    667   std::vector<unsigned char> data;
    668   statement.ColumnBlobAsVector(0, &data);
    669   thumbnail->thumbnail = base::RefCountedBytes::TakeVector(&data);
    670   thumbnail->thumbnail_score.boring_score = statement.ColumnDouble(1);
    671   thumbnail->thumbnail_score.good_clipping = statement.ColumnBool(2);
    672   thumbnail->thumbnail_score.at_top = statement.ColumnBool(3);
    673   thumbnail->thumbnail_score.time_at_snapshot =
    674       base::Time::FromInternalValue(statement.ColumnInt64(4));
    675   return true;
    676 }
    677 
    678 int TopSitesDatabase::GetURLRank(const MostVisitedURL& url) {
    679   sql::Statement select_statement(db_->GetCachedStatement(
    680       SQL_FROM_HERE,
    681       "SELECT url_rank "
    682       "FROM thumbnails WHERE url=?"));
    683   select_statement.BindString(0, url.url.spec());
    684   if (select_statement.Step())
    685     return select_statement.ColumnInt(0);
    686 
    687   return kRankOfNonExistingURL;
    688 }
    689 
    690 // Remove the record for this URL. Returns true iff removed successfully.
    691 bool TopSitesDatabase::RemoveURL(const MostVisitedURL& url) {
    692   int old_rank = GetURLRank(url);
    693   if (old_rank == kRankOfNonExistingURL)
    694     return false;
    695 
    696   sql::Transaction transaction(db_.get());
    697   transaction.Begin();
    698   if (old_rank != kRankOfForcedURL) {
    699     // Decrement all following ranks.
    700     sql::Statement shift_statement(db_->GetCachedStatement(
    701         SQL_FROM_HERE,
    702         "UPDATE thumbnails "
    703         "SET url_rank = url_rank - 1 "
    704         "WHERE url_rank > ?"));
    705     shift_statement.BindInt(0, old_rank);
    706 
    707     if (!shift_statement.Run())
    708       return false;
    709   }
    710 
    711   sql::Statement delete_statement(
    712       db_->GetCachedStatement(SQL_FROM_HERE,
    713                               "DELETE FROM thumbnails WHERE url = ?"));
    714   delete_statement.BindString(0, url.url.spec());
    715 
    716   if (!delete_statement.Run())
    717     return false;
    718 
    719   return transaction.Commit();
    720 }
    721 
    722 sql::Connection* TopSitesDatabase::CreateDB(const base::FilePath& db_name) {
    723   scoped_ptr<sql::Connection> db(new sql::Connection());
    724   // Settings copied from ThumbnailDatabase.
    725   db->set_histogram_tag("TopSites");
    726   db->set_error_callback(base::Bind(&DatabaseErrorCallback,
    727                                     db.get(), db_name));
    728   db->set_page_size(4096);
    729   db->set_cache_size(32);
    730 
    731   if (!db->Open(db_name))
    732     return NULL;
    733   return db.release();
    734 }
    735 
    736 }  // namespace history
    737