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/history_database.h"
      6 
      7 #include <algorithm>
      8 #include <set>
      9 #include <string>
     10 
     11 #include "base/command_line.h"
     12 #include "base/file_util.h"
     13 #include "base/metrics/histogram.h"
     14 #include "base/rand_util.h"
     15 #include "base/strings/string_util.h"
     16 #include "base/time/time.h"
     17 #include "sql/transaction.h"
     18 
     19 #if defined(OS_MACOSX)
     20 #include "base/mac/mac_util.h"
     21 #endif
     22 
     23 namespace history {
     24 
     25 namespace {
     26 
     27 // Current version number. We write databases at the "current" version number,
     28 // but any previous version that can read the "compatible" one can make do with
     29 // or database without *too* many bad effects.
     30 const int kCurrentVersionNumber = 28;
     31 const int kCompatibleVersionNumber = 16;
     32 const char kEarlyExpirationThresholdKey[] = "early_expiration_threshold";
     33 
     34 // Key in the meta table used to determine if we need to migrate thumbnails out
     35 // of history.
     36 const char kNeedsThumbnailMigrationKey[] = "needs_thumbnail_migration";
     37 
     38 }  // namespace
     39 
     40 HistoryDatabase::HistoryDatabase()
     41     : needs_version_17_migration_(false) {
     42 }
     43 
     44 HistoryDatabase::~HistoryDatabase() {
     45 }
     46 
     47 sql::InitStatus HistoryDatabase::Init(const base::FilePath& history_name) {
     48   db_.set_histogram_tag("History");
     49 
     50   // Set the exceptional sqlite error handler.
     51   db_.set_error_callback(error_callback_);
     52 
     53   // Set the database page size to something a little larger to give us
     54   // better performance (we're typically seek rather than bandwidth limited).
     55   // This only has an effect before any tables have been created, otherwise
     56   // this is a NOP. Must be a power of 2 and a max of 8192.
     57   db_.set_page_size(4096);
     58 
     59   // Set the cache size. The page size, plus a little extra, times this
     60   // value, tells us how much memory the cache will use maximum.
     61   // 1000 * 4kB = 4MB
     62   // TODO(brettw) scale this value to the amount of available memory.
     63   db_.set_cache_size(1000);
     64 
     65   // Note that we don't set exclusive locking here. That's done by
     66   // BeginExclusiveMode below which is called later (we have to be in shared
     67   // mode to start out for the in-memory backend to read the data).
     68 
     69   if (!db_.Open(history_name))
     70     return sql::INIT_FAILURE;
     71 
     72   // Wrap the rest of init in a tranaction. This will prevent the database from
     73   // getting corrupted if we crash in the middle of initialization or migration.
     74   sql::Transaction committer(&db_);
     75   if (!committer.Begin())
     76     return sql::INIT_FAILURE;
     77 
     78 #if defined(OS_MACOSX)
     79   // Exclude the history file from backups.
     80   base::mac::SetFileBackupExclusion(history_name);
     81 #endif
     82 
     83   // Prime the cache.
     84   db_.Preload();
     85 
     86   // Create the tables and indices.
     87   // NOTE: If you add something here, also add it to
     88   //       RecreateAllButStarAndURLTables.
     89   if (!meta_table_.Init(&db_, GetCurrentVersion(), kCompatibleVersionNumber))
     90     return sql::INIT_FAILURE;
     91   if (!CreateURLTable(false) || !InitVisitTable() ||
     92       !InitKeywordSearchTermsTable() || !InitDownloadTable() ||
     93       !InitSegmentTables())
     94     return sql::INIT_FAILURE;
     95   CreateMainURLIndex();
     96   CreateKeywordSearchTermsIndices();
     97 
     98   // TODO(benjhayden) Remove at some point.
     99   meta_table_.DeleteKey("next_download_id");
    100 
    101   // Version check.
    102   sql::InitStatus version_status = EnsureCurrentVersion();
    103   if (version_status != sql::INIT_OK)
    104     return version_status;
    105 
    106   return committer.Commit() ? sql::INIT_OK : sql::INIT_FAILURE;
    107 }
    108 
    109 void HistoryDatabase::ComputeDatabaseMetrics(
    110     const base::FilePath& history_name) {
    111     base::TimeTicks start_time = base::TimeTicks::Now();
    112   int64 file_size = 0;
    113   if (!file_util::GetFileSize(history_name, &file_size))
    114     return;
    115   int file_mb = static_cast<int>(file_size / (1024 * 1024));
    116   UMA_HISTOGRAM_MEMORY_MB("History.DatabaseFileMB", file_mb);
    117 
    118   sql::Statement url_count(db_.GetUniqueStatement("SELECT count(*) FROM urls"));
    119   if (!url_count.Step())
    120     return;
    121   UMA_HISTOGRAM_COUNTS("History.URLTableCount", url_count.ColumnInt(0));
    122 
    123   sql::Statement visit_count(db_.GetUniqueStatement(
    124       "SELECT count(*) FROM visits"));
    125   if (!visit_count.Step())
    126     return;
    127   UMA_HISTOGRAM_COUNTS("History.VisitTableCount", visit_count.ColumnInt(0));
    128 
    129   base::Time one_week_ago = base::Time::Now() - base::TimeDelta::FromDays(7);
    130   sql::Statement weekly_visit_sql(db_.GetUniqueStatement(
    131       "SELECT count(*) FROM visits WHERE visit_time > ?"));
    132   weekly_visit_sql.BindInt64(0, one_week_ago.ToInternalValue());
    133   int weekly_visit_count = 0;
    134   if (weekly_visit_sql.Step())
    135     weekly_visit_count = weekly_visit_sql.ColumnInt(0);
    136   UMA_HISTOGRAM_COUNTS("History.WeeklyVisitCount", weekly_visit_count);
    137 
    138   base::Time one_month_ago = base::Time::Now() - base::TimeDelta::FromDays(30);
    139   sql::Statement monthly_visit_sql(db_.GetUniqueStatement(
    140       "SELECT count(*) FROM visits WHERE visit_time > ? AND visit_time <= ?"));
    141   monthly_visit_sql.BindInt64(0, one_month_ago.ToInternalValue());
    142   monthly_visit_sql.BindInt64(1, one_week_ago.ToInternalValue());
    143   int older_visit_count = 0;
    144   if (monthly_visit_sql.Step())
    145     older_visit_count = monthly_visit_sql.ColumnInt(0);
    146   UMA_HISTOGRAM_COUNTS("History.MonthlyVisitCount",
    147                        older_visit_count + weekly_visit_count);
    148 
    149   UMA_HISTOGRAM_TIMES("History.DatabaseBasicMetricsTime",
    150                       base::TimeTicks::Now() - start_time);
    151 
    152   // Compute the advanced metrics even less often, pending timing data showing
    153   // that's not necessary.
    154   if (base::RandInt(1, 3) == 3) {
    155     start_time = base::TimeTicks::Now();
    156 
    157     // Collect all URLs visited within the last month.
    158     sql::Statement url_sql(db_.GetUniqueStatement(
    159         "SELECT url, last_visit_time FROM urls WHERE last_visit_time > ?"));
    160     url_sql.BindInt64(0, one_month_ago.ToInternalValue());
    161 
    162     // Count URLs (which will always be unique) and unique hosts within the last
    163     // week and last month.
    164     int week_url_count = 0;
    165     int month_url_count = 0;
    166     std::set<std::string> week_hosts;
    167     std::set<std::string> month_hosts;
    168     while (url_sql.Step()) {
    169       GURL url(url_sql.ColumnString(0));
    170       base::Time visit_time =
    171           base::Time::FromInternalValue(url_sql.ColumnInt64(1));
    172       ++month_url_count;
    173       month_hosts.insert(url.host());
    174       if (visit_time > one_week_ago) {
    175         ++week_url_count;
    176         week_hosts.insert(url.host());
    177       }
    178     }
    179     UMA_HISTOGRAM_COUNTS("History.WeeklyURLCount", week_url_count);
    180     UMA_HISTOGRAM_COUNTS_10000("History.WeeklyHostCount", week_hosts.size());
    181     UMA_HISTOGRAM_COUNTS("History.MonthlyURLCount", month_url_count);
    182     UMA_HISTOGRAM_COUNTS_10000("History.MonthlyHostCount", month_hosts.size());
    183     UMA_HISTOGRAM_TIMES("History.DatabaseAdvancedMetricsTime",
    184                         base::TimeTicks::Now() - start_time);
    185   }
    186 }
    187 
    188 void HistoryDatabase::BeginExclusiveMode() {
    189   // We can't use set_exclusive_locking() since that only has an effect before
    190   // the DB is opened.
    191   ignore_result(db_.Execute("PRAGMA locking_mode=EXCLUSIVE"));
    192 }
    193 
    194 // static
    195 int HistoryDatabase::GetCurrentVersion() {
    196   return kCurrentVersionNumber;
    197 }
    198 
    199 void HistoryDatabase::BeginTransaction() {
    200   db_.BeginTransaction();
    201 }
    202 
    203 void HistoryDatabase::CommitTransaction() {
    204   db_.CommitTransaction();
    205 }
    206 
    207 void HistoryDatabase::RollbackTransaction() {
    208   db_.RollbackTransaction();
    209 }
    210 
    211 bool HistoryDatabase::RecreateAllTablesButURL() {
    212   if (!DropVisitTable())
    213     return false;
    214   if (!InitVisitTable())
    215     return false;
    216 
    217   if (!DropKeywordSearchTermsTable())
    218     return false;
    219   if (!InitKeywordSearchTermsTable())
    220     return false;
    221 
    222   if (!DropSegmentTables())
    223     return false;
    224   if (!InitSegmentTables())
    225     return false;
    226 
    227   // We also add the supplementary URL indices at this point. This index is
    228   // over parts of the URL table that weren't automatically created when the
    229   // temporary URL table was
    230   CreateKeywordSearchTermsIndices();
    231   return true;
    232 }
    233 
    234 void HistoryDatabase::Vacuum() {
    235   DCHECK_EQ(0, db_.transaction_nesting()) <<
    236       "Can not have a transaction when vacuuming.";
    237   ignore_result(db_.Execute("VACUUM"));
    238 }
    239 
    240 void HistoryDatabase::TrimMemory(bool aggressively) {
    241   db_.TrimMemory(aggressively);
    242 }
    243 
    244 bool HistoryDatabase::Raze() {
    245   return db_.Raze();
    246 }
    247 
    248 void HistoryDatabase::ThumbnailMigrationDone() {
    249   meta_table_.SetValue(kNeedsThumbnailMigrationKey, 0);
    250 }
    251 
    252 bool HistoryDatabase::GetNeedsThumbnailMigration() {
    253   int value = 0;
    254   return (meta_table_.GetValue(kNeedsThumbnailMigrationKey, &value) &&
    255           value != 0);
    256 }
    257 
    258 bool HistoryDatabase::SetSegmentID(VisitID visit_id, SegmentID segment_id) {
    259   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    260       "UPDATE visits SET segment_id = ? WHERE id = ?"));
    261   s.BindInt64(0, segment_id);
    262   s.BindInt64(1, visit_id);
    263   DCHECK(db_.GetLastChangeCount() == 1);
    264 
    265   return s.Run();
    266 }
    267 
    268 SegmentID HistoryDatabase::GetSegmentID(VisitID visit_id) {
    269   sql::Statement s(db_.GetCachedStatement(SQL_FROM_HERE,
    270       "SELECT segment_id FROM visits WHERE id = ?"));
    271   s.BindInt64(0, visit_id);
    272 
    273   if (s.Step()) {
    274     if (s.ColumnType(0) == sql::COLUMN_TYPE_NULL)
    275       return 0;
    276     else
    277       return s.ColumnInt64(0);
    278   }
    279   return 0;
    280 }
    281 
    282 base::Time HistoryDatabase::GetEarlyExpirationThreshold() {
    283   if (!cached_early_expiration_threshold_.is_null())
    284     return cached_early_expiration_threshold_;
    285 
    286   int64 threshold;
    287   if (!meta_table_.GetValue(kEarlyExpirationThresholdKey, &threshold)) {
    288     // Set to a very early non-zero time, so it's before all history, but not
    289     // zero to avoid re-retrieval.
    290     threshold = 1L;
    291   }
    292 
    293   cached_early_expiration_threshold_ = base::Time::FromInternalValue(threshold);
    294   return cached_early_expiration_threshold_;
    295 }
    296 
    297 void HistoryDatabase::UpdateEarlyExpirationThreshold(base::Time threshold) {
    298   meta_table_.SetValue(kEarlyExpirationThresholdKey,
    299                        threshold.ToInternalValue());
    300   cached_early_expiration_threshold_ = threshold;
    301 }
    302 
    303 sql::Connection& HistoryDatabase::GetDB() {
    304   return db_;
    305 }
    306 
    307 // Migration -------------------------------------------------------------------
    308 
    309 sql::InitStatus HistoryDatabase::EnsureCurrentVersion() {
    310   // We can't read databases newer than we were designed for.
    311   if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
    312     LOG(WARNING) << "History database is too new.";
    313     return sql::INIT_TOO_NEW;
    314   }
    315 
    316   // NOTICE: If you are changing structures for things shared with the archived
    317   // history file like URLs, visits, or downloads, that will need migration as
    318   // well. Instead of putting such migration code in this class, it should be
    319   // in the corresponding file (url_database.cc, etc.) and called from here and
    320   // from the archived_database.cc.
    321 
    322   int cur_version = meta_table_.GetVersionNumber();
    323 
    324   // Put migration code here
    325 
    326   if (cur_version == 15) {
    327     if (!db_.Execute("DROP TABLE starred") || !DropStarredIDFromURLs()) {
    328       LOG(WARNING) << "Unable to update history database to version 16.";
    329       return sql::INIT_FAILURE;
    330     }
    331     ++cur_version;
    332     meta_table_.SetVersionNumber(cur_version);
    333     meta_table_.SetCompatibleVersionNumber(
    334         std::min(cur_version, kCompatibleVersionNumber));
    335   }
    336 
    337   if (cur_version == 16) {
    338 #if !defined(OS_WIN)
    339     // In this version we bring the time format on Mac & Linux in sync with the
    340     // Windows version so that profiles can be moved between computers.
    341     MigrateTimeEpoch();
    342 #endif
    343     // On all platforms we bump the version number, so on Windows this
    344     // migration is a NOP. We keep the compatible version at 16 since things
    345     // will basically still work, just history will be in the future if an
    346     // old version reads it.
    347     ++cur_version;
    348     meta_table_.SetVersionNumber(cur_version);
    349   }
    350 
    351   if (cur_version == 17) {
    352     // Version 17 was for thumbnails to top sites migration. We ended up
    353     // disabling it though, so 17->18 does nothing.
    354     ++cur_version;
    355     meta_table_.SetVersionNumber(cur_version);
    356   }
    357 
    358   if (cur_version == 18) {
    359     // This is the version prior to adding url_source column. We need to
    360     // migrate the database.
    361     cur_version = 19;
    362     meta_table_.SetVersionNumber(cur_version);
    363   }
    364 
    365   if (cur_version == 19) {
    366     cur_version++;
    367     meta_table_.SetVersionNumber(cur_version);
    368     // Set a key indicating we need to migrate thumbnails. When successfull the
    369     // key is removed (ThumbnailMigrationDone).
    370     meta_table_.SetValue(kNeedsThumbnailMigrationKey, 1);
    371   }
    372 
    373   if (cur_version == 20) {
    374     // This is the version prior to adding the visit_duration field in visits
    375     // database. We need to migrate the database.
    376     if (!MigrateVisitsWithoutDuration()) {
    377       LOG(WARNING) << "Unable to update history database to version 21.";
    378       return sql::INIT_FAILURE;
    379     }
    380     ++cur_version;
    381     meta_table_.SetVersionNumber(cur_version);
    382   }
    383 
    384   if (cur_version == 21) {
    385     // The android_urls table's data schemal was changed in version 21.
    386 #if defined(OS_ANDROID)
    387     if (!MigrateToVersion22()) {
    388       LOG(WARNING) << "Unable to migrate the android_urls table to version 22";
    389     }
    390 #endif
    391     ++cur_version;
    392     meta_table_.SetVersionNumber(cur_version);
    393   }
    394 
    395   if (cur_version == 22) {
    396     if (!MigrateDownloadsState()) {
    397       LOG(WARNING) << "Unable to fix invalid downloads state values";
    398       // Invalid state values may cause crashes.
    399       return sql::INIT_FAILURE;
    400     }
    401     cur_version++;
    402     meta_table_.SetVersionNumber(cur_version);
    403   }
    404 
    405   if (cur_version == 23) {
    406     if (!MigrateDownloadsReasonPathsAndDangerType()) {
    407       LOG(WARNING) << "Unable to upgrade download interrupt reason and paths";
    408       // Invalid state values may cause crashes.
    409       return sql::INIT_FAILURE;
    410     }
    411     cur_version++;
    412     meta_table_.SetVersionNumber(cur_version);
    413   }
    414 
    415   if (cur_version == 24) {
    416     if (!MigratePresentationIndex()) {
    417       LOG(WARNING) << "Unable to migrate history to version 25";
    418       return sql::INIT_FAILURE;
    419     }
    420     cur_version++;
    421     meta_table_.SetVersionNumber(cur_version);
    422   }
    423 
    424   if (cur_version == 25) {
    425     if (!MigrateReferrer()) {
    426       LOG(WARNING) << "Unable to migrate history to version 26";
    427       return sql::INIT_FAILURE;
    428     }
    429     cur_version++;
    430     meta_table_.SetVersionNumber(cur_version);
    431   }
    432 
    433   if (cur_version == 26) {
    434     if (!MigrateDownloadedByExtension()) {
    435       LOG(WARNING) << "Unable to migrate history to version 27";
    436       return sql::INIT_FAILURE;
    437     }
    438     cur_version++;
    439     meta_table_.SetVersionNumber(cur_version);
    440   }
    441 
    442   if (cur_version == 27) {
    443     if (!MigrateDownloadValidators()) {
    444       LOG(WARNING) << "Unable to migrate history to version 28";
    445       return sql::INIT_FAILURE;
    446     }
    447     cur_version++;
    448     meta_table_.SetVersionNumber(cur_version);
    449   }
    450 
    451   // When the version is too old, we just try to continue anyway, there should
    452   // not be a released product that makes a database too old for us to handle.
    453   LOG_IF(WARNING, cur_version < GetCurrentVersion()) <<
    454          "History database version " << cur_version << " is too old to handle.";
    455 
    456   return sql::INIT_OK;
    457 }
    458 
    459 #if !defined(OS_WIN)
    460 void HistoryDatabase::MigrateTimeEpoch() {
    461   // Update all the times in the URLs and visits table in the main database.
    462   ignore_result(db_.Execute(
    463       "UPDATE urls "
    464       "SET last_visit_time = last_visit_time + 11644473600000000 "
    465       "WHERE id IN (SELECT id FROM urls WHERE last_visit_time > 0);"));
    466   ignore_result(db_.Execute(
    467       "UPDATE visits "
    468       "SET visit_time = visit_time + 11644473600000000 "
    469       "WHERE id IN (SELECT id FROM visits WHERE visit_time > 0);"));
    470   ignore_result(db_.Execute(
    471       "UPDATE segment_usage "
    472       "SET time_slot = time_slot + 11644473600000000 "
    473       "WHERE id IN (SELECT id FROM segment_usage WHERE time_slot > 0);"));
    474 
    475   // Erase all the full text index files. These will take a while to update and
    476   // are less important, so we just blow them away. Same with the archived
    477   // database.
    478   needs_version_17_migration_ = true;
    479 }
    480 #endif
    481 
    482 }  // namespace history
    483