summaryrefslogtreecommitdiffstats
path: root/chrome/browser/history/visit_database.cc
blob: 09f1231df896b7dac8d22391fcbaa98feb7ba6c8 (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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
// 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 <algorithm>
#include <limits>
#include <map>
#include <set>

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

#include "chrome/browser/history/url_database.h"
#include "chrome/common/page_transition_types.h"

using base::Time;

// Rows, in order, of the visit table.
#define HISTORY_VISIT_ROW_FIELDS \
  " id,url,visit_time,from_visit,transition,segment_id,is_indexed "

namespace history {

VisitDatabase::VisitDatabase() {
}

VisitDatabase::~VisitDatabase() {
}

bool VisitDatabase::InitVisitTable() {
  if (!DoesSqliteTableExist(GetDB(), "visits")) {
    if (sqlite3_exec(GetDB(), "CREATE TABLE visits("
        "id INTEGER PRIMARY KEY,"
        "url INTEGER NOT NULL," // key of the URL this corresponds to
        "visit_time INTEGER NOT NULL,"
        "from_visit INTEGER,"
        "transition INTEGER DEFAULT 0 NOT NULL,"
        "segment_id INTEGER,"
        "is_indexed BOOLEAN)",  // True when we have indexed data for this visit.
        NULL, NULL, NULL) != SQLITE_OK)
      return false;
  } else if (!DoesSqliteColumnExist(GetDB(), "visits",
                                    "is_indexed", "BOOLEAN")) {
    // Old versions don't have the is_indexed column, we can just add that and
    // not worry about different database revisions, since old ones will
    // continue to work.
    //
    // TODO(brettw) this should be removed once we think everybody has been
    // updated (added early Mar 2008).
    if (sqlite3_exec(GetDB(),
                     "ALTER TABLE visits ADD COLUMN is_indexed BOOLEAN",
                     NULL, NULL, NULL) != SQLITE_OK)
      return false;
  }

  // Index over url so we can quickly find visits for a page. This will just
  // fail if it already exists and we'll ignore it.
  sqlite3_exec(GetDB(), "CREATE INDEX visits_url_index ON visits (url)",
               NULL, NULL, NULL);

  // Create an index over from visits so that we can efficiently find
  // referrers and redirects. Ignore failures because it likely already exists.
  sqlite3_exec(GetDB(), "CREATE INDEX visits_from_index ON visits (from_visit)",
               NULL, NULL, NULL);

  // Create an index over time so that we can efficiently find the visits in a
  // given time range (most history views are time-based). Ignore failures
  // because it likely already exists.
  sqlite3_exec(GetDB(), "CREATE INDEX visits_time_index ON visits (visit_time)",
               NULL, NULL, NULL);

  return true;
}

bool VisitDatabase::DropVisitTable() {
  // This will also drop the indices over the table.
  return sqlite3_exec(GetDB(), "DROP TABLE visits", NULL, NULL, NULL) ==
      SQLITE_OK;
}

// Must be in sync with HISTORY_VISIT_ROW_FIELDS.
// static
void VisitDatabase::FillVisitRow(SQLStatement& statement, VisitRow* visit) {
  visit->visit_id = statement.column_int64(0);
  visit->url_id = statement.column_int64(1);
  visit->visit_time = Time::FromInternalValue(statement.column_int64(2));
  visit->referring_visit = statement.column_int64(3);
  visit->transition = PageTransition::FromInt(statement.column_int(4));
  visit->segment_id = statement.column_int64(5);
  visit->is_indexed = !!statement.column_int(6);
}

// static
void VisitDatabase::FillVisitVector(SQLStatement& statement,
                                    VisitVector* visits) {
  while (statement.step() == SQLITE_ROW) {
    history::VisitRow visit;
    FillVisitRow(statement, &visit);
    visits->push_back(visit);
  }
}

VisitID VisitDatabase::AddVisit(VisitRow* visit) {
  SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(),
      "INSERT INTO visits "
      "(url, visit_time, from_visit, transition, segment_id, is_indexed) "
      "VALUES (?,?,?,?,?,?)");
  if (!statement.is_valid())
    return 0;

  statement->bind_int64(0, visit->url_id);
  statement->bind_int64(1, visit->visit_time.ToInternalValue());
  statement->bind_int64(2, visit->referring_visit);
  statement->bind_int64(3, visit->transition);
  statement->bind_int64(4, visit->segment_id);
  statement->bind_int64(5, visit->is_indexed);
  if (statement->step() != SQLITE_DONE)
    return 0;

  visit->visit_id = sqlite3_last_insert_rowid(GetDB());
  return visit->visit_id;
}

void VisitDatabase::DeleteVisit(const VisitRow& visit) {
  // Patch around this visit. Any visits that this went to will now have their
  // "source" be the deleted visit's source.
  SQLITE_UNIQUE_STATEMENT(update_chain, GetStatementCache(),
                          "UPDATE visits SET from_visit=? "
                          "WHERE from_visit=?");
  if (!update_chain.is_valid())
    return;
  update_chain->bind_int64(0, visit.referring_visit);
  update_chain->bind_int64(1, visit.visit_id);
  update_chain->step();

  // Now delete the actual visit.
  SQLITE_UNIQUE_STATEMENT(del, GetStatementCache(),
                          "DELETE FROM visits WHERE id=?");
  if (!del.is_valid())
    return;
  del->bind_int64(0, visit.visit_id);
  del->step();
}

bool VisitDatabase::GetRowForVisit(VisitID visit_id, VisitRow* out_visit) {
  SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(),
      "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits WHERE id=?");
  if (!statement.is_valid())
    return false;

  statement->bind_int64(0, visit_id);
  if (statement->step() != SQLITE_ROW)
    return false;

  FillVisitRow(*statement, out_visit);
  return true;
}

bool VisitDatabase::UpdateVisitRow(const VisitRow& visit) {
  SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(),
      "UPDATE visits SET "
      "url=?,visit_time=?,from_visit=?,transition=?,segment_id=?,is_indexed=? "
      "WHERE id=?");
  if (!statement.is_valid())
    return false;

  statement->bind_int64(0, visit.url_id);
  statement->bind_int64(1, visit.visit_time.ToInternalValue());
  statement->bind_int64(2, visit.referring_visit);
  statement->bind_int64(3, visit.transition);
  statement->bind_int64(4, visit.segment_id);
  statement->bind_int64(5, visit.is_indexed);
  statement->bind_int64(6, visit.visit_id);
  return statement->step() == SQLITE_DONE;
}

bool VisitDatabase::GetVisitsForURL(URLID url_id, VisitVector* visits) {
  visits->clear();

  SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(),
      "SELECT" HISTORY_VISIT_ROW_FIELDS
      "FROM visits "
      "WHERE url=? "
      "ORDER BY visit_time ASC");
  if (!statement.is_valid())
    return false;

  statement->bind_int64(0, url_id);
  FillVisitVector(*statement, visits);
  return true;
}

void VisitDatabase::GetAllVisitsInRange(Time begin_time, Time end_time,
                                        int max_results,
                                        VisitVector* visits) {
  visits->clear();

  SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(),
      "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
      "WHERE visit_time >= ? AND visit_time < ?"
      "ORDER BY visit_time LIMIT ?");
  if (!statement.is_valid())
    return;

  // See GetVisibleVisitsInRange for more info on how these times are bound.
  int64 end = end_time.ToInternalValue();
  statement->bind_int64(0, begin_time.ToInternalValue());
  statement->bind_int64(1, end ? end : std::numeric_limits<int64>::max());
  statement->bind_int64(2,
      max_results ? max_results : std::numeric_limits<int64>::max());

  FillVisitVector(*statement, visits);
}

void VisitDatabase::GetVisibleVisitsInRange(Time begin_time, Time end_time,
                                            bool most_recent_visit_only,
                                            int max_count,
                                            VisitVector* visits) {
  visits->clear();
  // The visit_time values can be duplicated in a redirect chain, so we sort
  // by id too, to ensure a consistent ordering just in case.
  SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(),
      "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
      "WHERE visit_time >= ? AND visit_time < ? "
      "AND (transition & ?) != 0 "  // CHAIN_END
      "AND (transition & ?) NOT IN (?, ?) "  // NO SUBFRAME
      "ORDER BY visit_time DESC, id DESC");
  if (!statement.is_valid())
    return;

  // Note that we use min/max values for querying unlimited ranges of time using
  // the same statement. Since the time has an index, this will be about the
  // same amount of work as just doing a query for everything with no qualifier.
  int64 end = end_time.ToInternalValue();
  statement->bind_int64(0, begin_time.ToInternalValue());
  statement->bind_int64(1, end ? end : std::numeric_limits<int64>::max());
  statement->bind_int(2, PageTransition::CHAIN_END);
  statement->bind_int(3, PageTransition::CORE_MASK);
  statement->bind_int(4, PageTransition::AUTO_SUBFRAME);
  statement->bind_int(5, PageTransition::MANUAL_SUBFRAME);

  std::set<URLID> found_urls;
  while (statement->step() == SQLITE_ROW) {
    VisitRow visit;
    FillVisitRow(*statement, &visit);
    if (most_recent_visit_only) {
      // Make sure the URL this visit corresponds to is unique if required.
      if (found_urls.find(visit.url_id) != found_urls.end())
        continue;
      found_urls.insert(visit.url_id);
    }
    visits->push_back(visit);

    if (max_count > 0 && static_cast<int>(visits->size()) >= max_count)
      break;
  }
}

VisitID VisitDatabase::GetMostRecentVisitForURL(URLID url_id,
                                                VisitRow* visit_row) {
  // The visit_time values can be duplicated in a redirect chain, so we sort
  // by id too, to ensure a consistent ordering just in case.
  SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(),
      "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
      "WHERE url=? "
      "ORDER BY visit_time DESC, id DESC "
      "LIMIT 1");
  if (!statement.is_valid())
    return 0;

  statement->bind_int64(0, url_id);
  if (statement->step() != SQLITE_ROW)
    return 0;  // No visits for this URL.

  if (visit_row) {
    FillVisitRow(*statement, visit_row);
    return visit_row->visit_id;
  }
  return statement->column_int64(0);
}

bool VisitDatabase::GetMostRecentVisitsForURL(URLID url_id,
                                              int max_results,
                                              VisitVector* visits) {
  visits->clear();

  // The visit_time values can be duplicated in a redirect chain, so we sort
  // by id too, to ensure a consistent ordering just in case.
  SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(),
      "SELECT" HISTORY_VISIT_ROW_FIELDS
      "FROM visits "
      "WHERE url=? "
      "ORDER BY visit_time DESC, id DESC "
      "LIMIT ?");
  if (!statement.is_valid())
    return false;

  statement->bind_int64(0, url_id);
  statement->bind_int(1, max_results);
  FillVisitVector(*statement, visits);
  return true;
}

bool VisitDatabase::GetRedirectFromVisit(VisitID from_visit,
                                         VisitID* to_visit,
                                         GURL* to_url) {
  SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(),
      "SELECT v.id,u.url "
      "FROM visits v JOIN urls u ON v.url = u.id "
      "WHERE v.from_visit = ? "
      "AND (v.transition & ?) != 0");  // IS_REDIRECT_MASK
  if (!statement.is_valid())
    return false;

  statement->bind_int64(0, from_visit);
  statement->bind_int(1, PageTransition::IS_REDIRECT_MASK);

  if (statement->step() != SQLITE_ROW)
    return false;  // No redirect from this visit.
  if (to_visit)
    *to_visit = statement->column_int64(0);
  if (to_url)
    *to_url = GURL(statement->column_string(1));
  return true;
}

bool VisitDatabase::GetVisitCountToHost(const GURL& url,
                                        int* count,
                                        Time* first_visit) {
  if (!url.SchemeIs("http") && !url.SchemeIs("https"))
    return false;

  // We need to search for URLs with a matching host/port. One way to query for
  // this is to use the LIKE operator, eg 'url LIKE http://google.com/%'. This
  // is inefficient though in that it doesn't use the index and each entry must
  // be visited. The same query can be executed by using >= and < operator.
  // The query becomes:
  // 'url >= http://google.com/' and url < http://google.com0'.
  // 0 is used as it is one character greater than '/'.
  GURL search_url(url);
  const std::string host_query_min = search_url.GetOrigin().spec();

  if (host_query_min.empty())
    return false;

  std::string host_query_max = host_query_min;
  host_query_max[host_query_max.size() - 1] = '0';

  SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(),
      "SELECT MIN(v.visit_time), COUNT(*) "
      "FROM visits v INNER JOIN urls u ON v.url = u.id "
      "WHERE (u.url >= ? AND u.url < ?)");
  if (!statement.is_valid())
    return false;

  statement->bind_string(0, host_query_min);
  statement->bind_string(1, host_query_max);

  if (statement->step() != SQLITE_ROW) {
    // We've never been to this page before.
    *count = 0;
    return true;
  }

  *first_visit = Time::FromInternalValue(statement->column_int64(0));
  *count = statement->column_int(1);
  return true;
}

}  // namespace history