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