summaryrefslogtreecommitdiffstats
path: root/chrome/browser/history/history_database.cc
blob: 8a1bb59ba0661506509f1912db3b6e07b3f4f610 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/history/history_database.h"

#include <algorithm>
#include <set>

#include "base/string_util.h"

// Only needed for migration.
#include "base/file_util.h"
#include "chrome/browser/history/text_database_manager.h"

namespace history {

namespace {

// Current version number.
static const int kCurrentVersionNumber = 16;
static const int kCompatibleVersionNumber = 16;

}  // namespace

HistoryDatabase::HistoryDatabase()
    : transaction_nesting_(0),
      db_(NULL) {
}

HistoryDatabase::~HistoryDatabase() {
}

InitStatus HistoryDatabase::Init(const std::wstring& history_name,
                                 const std::wstring& bookmarks_path) {
  // Open the history database, using the narrow version of open indicates to
  // sqlite that we want the database to be in UTF-8 if it doesn't already
  // exist.
  DCHECK(!db_) << "Already initialized!";
  if (sqlite3_open(WideToUTF8(history_name).c_str(), &db_) != SQLITE_OK)
    return INIT_FAILURE;
  statement_cache_ = new SqliteStatementCache;
  DBCloseScoper scoper(&db_, &statement_cache_);

  // Set the database page size to something a little larger to give us
  // better performance (we're typically seek rather than bandwidth limited).
  // This only has an effect before any tables have been created, otherwise
  // this is a NOP. Must be a power of 2 and a max of 8192.
  sqlite3_exec(db_, "PRAGMA page_size=4096", NULL, NULL, NULL);

  // Increase the cache size. The page size, plus a little extra, times this
  // value, tells us how much memory the cache will use maximum.
  // 6000 * 4MB = 24MB
  // TODO(brettw) scale this value to the amount of available memory.
  sqlite3_exec(db_, "PRAGMA cache_size=6000", NULL, NULL, NULL);

  // Wrap the rest of init in a tranaction. This will prevent the database from
  // getting corrupted if we crash in the middle of initialization or migration.
  TransactionScoper transaction(this);

  // Make sure the statement cache is properly initialized.
  statement_cache_->set_db(db_);

  // Prime the cache. See the header file's documentation for this function.
  PrimeCache();

  // Create the tables and indices.
  // NOTE: If you add something here, also add it to
  //       RecreateAllButStarAndURLTables.
  if (!meta_table_.Init(std::string(), kCurrentVersionNumber,
                        kCompatibleVersionNumber, db_))
    return INIT_FAILURE;
  if (!CreateURLTable(false) || !InitVisitTable() ||
      !InitKeywordSearchTermsTable() || !InitDownloadTable() ||
      !InitSegmentTables())
    return INIT_FAILURE;
  CreateMainURLIndex();
  CreateSupplimentaryURLIndices();

  // Version check.
  InitStatus version_status = EnsureCurrentVersion(bookmarks_path);
  if (version_status != INIT_OK)
    return version_status;

  // Succeeded: keep the DB open by detaching the auto-closer.
  scoper.Detach();
  db_closer_.Attach(&db_, &statement_cache_);
  return INIT_OK;
}

void HistoryDatabase::BeginExclusiveMode() {
  sqlite3_exec(db_, "PRAGMA locking_mode=EXCLUSIVE", NULL, NULL, NULL);
}

void HistoryDatabase::PrimeCache() {
  // A statement must be open for the preload command to work. If the meta
  // table can't be read, it probably means this is a new database and there
  // is nothing to preload (so it's OK we do nothing).
  SQLStatement dummy;
  if (dummy.prepare(db_, "SELECT * from meta") != SQLITE_OK)
    return;
  if (dummy.step() != SQLITE_ROW)
    return;

  sqlite3Preload(db_);
}

// static
int HistoryDatabase::GetCurrentVersion() {
  return kCurrentVersionNumber;
}

void HistoryDatabase::BeginTransaction() {
  DCHECK(db_);
  if (transaction_nesting_ == 0) {
    int rv = sqlite3_exec(db_, "BEGIN TRANSACTION", NULL, NULL, NULL);
    DCHECK(rv == SQLITE_OK) << "Failed to begin transaction";
  }
  transaction_nesting_++;
}

void HistoryDatabase::CommitTransaction() {
  DCHECK(db_);
  DCHECK(transaction_nesting_ > 0) << "Committing too many transactions";
  transaction_nesting_--;
  if (transaction_nesting_ == 0) {
    int rv = sqlite3_exec(db_, "COMMIT", NULL, NULL, NULL);
    DCHECK(rv == SQLITE_OK) << "Failed to commit transaction";
  }
}

bool HistoryDatabase::RecreateAllTablesButURL() {
  if (!DropVisitTable())
    return false;
  if (!InitVisitTable())
    return false;

  if (!DropKeywordSearchTermsTable())
    return false;
  if (!InitKeywordSearchTermsTable())
    return false;

  if (!DropSegmentTables())
    return false;
  if (!InitSegmentTables())
    return false;

  // We also add the supplementary URL indices at this point. This index is
  // over parts of the URL table that weren't automatically created when the
  // temporary URL table was
  CreateSupplimentaryURLIndices();
  return true;
}

void HistoryDatabase::Vacuum() {
  DCHECK(transaction_nesting_ == 0) <<
      "Can not have a transaction when vacuuming.";
  sqlite3_exec(db_, "VACUUM", NULL, NULL, NULL);
}

bool HistoryDatabase::SetSegmentID(VisitID visit_id, SegmentID segment_id) {
  SQLStatement s;
  if (s.prepare(db_, "UPDATE visits SET segment_id = ? WHERE id = ?") !=
      SQLITE_OK) {
    NOTREACHED();
    return false;
  }
  s.bind_int64(0, segment_id);
  s.bind_int64(1, visit_id);
  return s.step() == SQLITE_DONE;
}

SegmentID HistoryDatabase::GetSegmentID(VisitID visit_id) {
  SQLStatement s;
  if (s.prepare(db_, "SELECT segment_id FROM visits WHERE id = ?")
      != SQLITE_OK) {
    NOTREACHED();
    return 0;
  }

  s.bind_int64(0, visit_id);
  if (s.step() == SQLITE_ROW) {
    if (s.column_type(0) == SQLITE_NULL)
      return 0;
    else
      return s.column_int64(0);
  }
  return 0;
}

sqlite3* HistoryDatabase::GetDB() {
  return db_;
}

SqliteStatementCache& HistoryDatabase::GetStatementCache() {
  return *statement_cache_;
}

// Migration -------------------------------------------------------------------

InitStatus HistoryDatabase::EnsureCurrentVersion(
    const std::wstring& tmp_bookmarks_path) {
  // We can't read databases newer than we were designed for.
  if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) {
    LOG(WARNING) << "History database is too new.";
    return INIT_TOO_NEW;
  }

  // NOTICE: If you are changing structures for things shared with the archived
  // history file like URLs, visits, or downloads, that will need migration as
  // well. Instead of putting such migration code in this class, it should be
  // in the corresponding file (url_database.cc, etc.) and called from here and
  // from the archived_database.cc.

  int cur_version = meta_table_.GetVersionNumber();

  // Put migration code here

  if (cur_version == 15) {
    if (!MigrateBookmarksToFile(tmp_bookmarks_path) ||
        !DropStarredIDFromURLs()) {
      LOG(WARNING) << "Unable to update history database to version 16.";
      return INIT_FAILURE;
    }
    ++cur_version;
    meta_table_.SetVersionNumber(cur_version);
    meta_table_.SetCompatibleVersionNumber(
        std::min(cur_version, kCompatibleVersionNumber));
  }

  // When the version is too old, we just try to continue anyway, there should
  // not be a released product that makes a database too old for us to handle.
  LOG_IF(WARNING, cur_version < kCurrentVersionNumber) <<
      "History database version " << cur_version << " is too old to handle.";

  return INIT_OK;
}

}  // namespace history