• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/visit_database.h"
6 
7 #include <algorithm>
8 #include <limits>
9 #include <map>
10 #include <set>
11 
12 #include "base/logging.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "chrome/browser/history/url_database.h"
15 #include "chrome/browser/history/visit_filter.h"
16 #include "chrome/common/url_constants.h"
17 #include "content/public/common/page_transition_types.h"
18 #include "sql/statement.h"
19 
20 namespace history {
21 
VisitDatabase()22 VisitDatabase::VisitDatabase() {
23 }
24 
~VisitDatabase()25 VisitDatabase::~VisitDatabase() {
26 }
27 
InitVisitTable()28 bool VisitDatabase::InitVisitTable() {
29   if (!GetDB().DoesTableExist("visits")) {
30     if (!GetDB().Execute("CREATE TABLE visits("
31         "id INTEGER PRIMARY KEY,"
32         "url INTEGER NOT NULL," // key of the URL this corresponds to
33         "visit_time INTEGER NOT NULL,"
34         "from_visit INTEGER,"
35         "transition INTEGER DEFAULT 0 NOT NULL,"
36         "segment_id INTEGER,"
37         // Some old DBs may have an "is_indexed" field here, but this is no
38         // longer used and should NOT be read or written from any longer.
39         "visit_duration INTEGER DEFAULT 0 NOT NULL)"))
40       return false;
41   }
42 
43   // Visit source table contains the source information for all the visits. To
44   // save space, we do not record those user browsed visits which would be the
45   // majority in this table. Only other sources are recorded.
46   // Due to the tight relationship between visit_source and visits table, they
47   // should be created and dropped at the same time.
48   if (!GetDB().DoesTableExist("visit_source")) {
49     if (!GetDB().Execute("CREATE TABLE visit_source("
50                          "id INTEGER PRIMARY KEY,source INTEGER NOT NULL)"))
51         return false;
52   }
53 
54   // Index over url so we can quickly find visits for a page.
55   if (!GetDB().Execute(
56           "CREATE INDEX IF NOT EXISTS visits_url_index ON visits (url)"))
57     return false;
58 
59   // Create an index over from visits so that we can efficiently find
60   // referrers and redirects.
61   if (!GetDB().Execute(
62           "CREATE INDEX IF NOT EXISTS visits_from_index ON "
63           "visits (from_visit)"))
64     return false;
65 
66   // Create an index over time so that we can efficiently find the visits in a
67   // given time range (most history views are time-based).
68   if (!GetDB().Execute(
69           "CREATE INDEX IF NOT EXISTS visits_time_index ON "
70           "visits (visit_time)"))
71     return false;
72 
73   return true;
74 }
75 
DropVisitTable()76 bool VisitDatabase::DropVisitTable() {
77   // This will also drop the indices over the table.
78   return
79       GetDB().Execute("DROP TABLE IF EXISTS visit_source") &&
80       GetDB().Execute("DROP TABLE visits");
81 }
82 
83 // Must be in sync with HISTORY_VISIT_ROW_FIELDS.
84 // static
FillVisitRow(sql::Statement & statement,VisitRow * visit)85 void VisitDatabase::FillVisitRow(sql::Statement& statement, VisitRow* visit) {
86   visit->visit_id = statement.ColumnInt64(0);
87   visit->url_id = statement.ColumnInt64(1);
88   visit->visit_time = base::Time::FromInternalValue(statement.ColumnInt64(2));
89   visit->referring_visit = statement.ColumnInt64(3);
90   visit->transition = content::PageTransitionFromInt(statement.ColumnInt(4));
91   visit->segment_id = statement.ColumnInt64(5);
92   visit->visit_duration =
93       base::TimeDelta::FromInternalValue(statement.ColumnInt64(6));
94 }
95 
96 // static
FillVisitVector(sql::Statement & statement,VisitVector * visits)97 bool VisitDatabase::FillVisitVector(sql::Statement& statement,
98                                     VisitVector* visits) {
99   if (!statement.is_valid())
100     return false;
101 
102   while (statement.Step()) {
103     history::VisitRow visit;
104     FillVisitRow(statement, &visit);
105     visits->push_back(visit);
106   }
107 
108   return statement.Succeeded();
109 }
110 
111 // static
FillVisitVectorWithOptions(sql::Statement & statement,const QueryOptions & options,VisitVector * visits)112 bool VisitDatabase::FillVisitVectorWithOptions(sql::Statement& statement,
113                                                const QueryOptions& options,
114                                                VisitVector* visits) {
115   std::set<URLID> found_urls;
116 
117   // Keeps track of the day that |found_urls| is holding the URLs for, in order
118   // to handle removing per-day duplicates.
119   base::Time found_urls_midnight;
120 
121   while (statement.Step()) {
122     VisitRow visit;
123     FillVisitRow(statement, &visit);
124 
125     if (options.duplicate_policy != QueryOptions::KEEP_ALL_DUPLICATES) {
126       if (options.duplicate_policy == QueryOptions::REMOVE_DUPLICATES_PER_DAY &&
127           found_urls_midnight != visit.visit_time.LocalMidnight()) {
128         found_urls.clear();
129         found_urls_midnight = visit.visit_time.LocalMidnight();
130       }
131       // Make sure the URL this visit corresponds to is unique.
132       if (found_urls.find(visit.url_id) != found_urls.end())
133         continue;
134       found_urls.insert(visit.url_id);
135     }
136 
137     if (static_cast<int>(visits->size()) >= options.EffectiveMaxCount())
138       return true;
139     visits->push_back(visit);
140   }
141   return false;
142 }
143 
AddVisit(VisitRow * visit,VisitSource source)144 VisitID VisitDatabase::AddVisit(VisitRow* visit, VisitSource source) {
145   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
146       "INSERT INTO visits "
147       "(url, visit_time, from_visit, transition, segment_id, "
148       "visit_duration) VALUES (?,?,?,?,?,?)"));
149   statement.BindInt64(0, visit->url_id);
150   statement.BindInt64(1, visit->visit_time.ToInternalValue());
151   statement.BindInt64(2, visit->referring_visit);
152   statement.BindInt64(3, visit->transition);
153   statement.BindInt64(4, visit->segment_id);
154   statement.BindInt64(5, visit->visit_duration.ToInternalValue());
155 
156   if (!statement.Run()) {
157     VLOG(0) << "Failed to execute visit insert statement:  "
158             << "url_id = " << visit->url_id;
159     return 0;
160   }
161 
162   visit->visit_id = GetDB().GetLastInsertRowId();
163 
164   if (source != SOURCE_BROWSED) {
165     // Record the source of this visit when it is not browsed.
166     sql::Statement statement1(GetDB().GetCachedStatement(SQL_FROM_HERE,
167         "INSERT INTO visit_source (id, source) VALUES (?,?)"));
168     statement1.BindInt64(0, visit->visit_id);
169     statement1.BindInt64(1, source);
170 
171     if (!statement1.Run()) {
172       VLOG(0) << "Failed to execute visit_source insert statement:  "
173               << "id = " << visit->visit_id;
174       return 0;
175     }
176   }
177 
178   return visit->visit_id;
179 }
180 
DeleteVisit(const VisitRow & visit)181 void VisitDatabase::DeleteVisit(const VisitRow& visit) {
182   // Patch around this visit. Any visits that this went to will now have their
183   // "source" be the deleted visit's source.
184   sql::Statement update_chain(GetDB().GetCachedStatement(SQL_FROM_HERE,
185       "UPDATE visits SET from_visit=? WHERE from_visit=?"));
186   update_chain.BindInt64(0, visit.referring_visit);
187   update_chain.BindInt64(1, visit.visit_id);
188   if (!update_chain.Run())
189     return;
190 
191   // Now delete the actual visit.
192   sql::Statement del(GetDB().GetCachedStatement(SQL_FROM_HERE,
193       "DELETE FROM visits WHERE id=?"));
194   del.BindInt64(0, visit.visit_id);
195   if (!del.Run())
196     return;
197 
198   // Try to delete the entry in visit_source table as well.
199   // If the visit was browsed, there is no corresponding entry in visit_source
200   // table, and nothing will be deleted.
201   del.Assign(GetDB().GetCachedStatement(SQL_FROM_HERE,
202              "DELETE FROM visit_source WHERE id=?"));
203   del.BindInt64(0, visit.visit_id);
204   del.Run();
205 }
206 
GetRowForVisit(VisitID visit_id,VisitRow * out_visit)207 bool VisitDatabase::GetRowForVisit(VisitID visit_id, VisitRow* out_visit) {
208   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
209       "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits WHERE id=?"));
210   statement.BindInt64(0, visit_id);
211 
212   if (!statement.Step())
213     return false;
214 
215   FillVisitRow(statement, out_visit);
216 
217   // We got a different visit than we asked for, something is wrong.
218   DCHECK_EQ(visit_id, out_visit->visit_id);
219   if (visit_id != out_visit->visit_id)
220     return false;
221 
222   return true;
223 }
224 
UpdateVisitRow(const VisitRow & visit)225 bool VisitDatabase::UpdateVisitRow(const VisitRow& visit) {
226   // Don't store inconsistent data to the database.
227   DCHECK_NE(visit.visit_id, visit.referring_visit);
228   if (visit.visit_id == visit.referring_visit)
229     return false;
230 
231   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
232       "UPDATE visits SET "
233       "url=?,visit_time=?,from_visit=?,transition=?,segment_id=?,"
234       "visit_duration=? WHERE id=?"));
235   statement.BindInt64(0, visit.url_id);
236   statement.BindInt64(1, visit.visit_time.ToInternalValue());
237   statement.BindInt64(2, visit.referring_visit);
238   statement.BindInt64(3, visit.transition);
239   statement.BindInt64(4, visit.segment_id);
240   statement.BindInt64(5, visit.visit_duration.ToInternalValue());
241   statement.BindInt64(6, visit.visit_id);
242 
243   return statement.Run();
244 }
245 
GetVisitsForURL(URLID url_id,VisitVector * visits)246 bool VisitDatabase::GetVisitsForURL(URLID url_id, VisitVector* visits) {
247   visits->clear();
248 
249   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
250       "SELECT" HISTORY_VISIT_ROW_FIELDS
251       "FROM visits "
252       "WHERE url=? "
253       "ORDER BY visit_time ASC"));
254   statement.BindInt64(0, url_id);
255   return FillVisitVector(statement, visits);
256 }
257 
GetVisibleVisitsForURL(URLID url_id,const QueryOptions & options,VisitVector * visits)258 bool VisitDatabase::GetVisibleVisitsForURL(URLID url_id,
259                                            const QueryOptions& options,
260                                            VisitVector* visits) {
261   visits->clear();
262 
263   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
264       "SELECT" HISTORY_VISIT_ROW_FIELDS
265       "FROM visits "
266       "WHERE url=? AND visit_time >= ? AND visit_time < ? "
267       "AND (transition & ?) != 0 "  // CHAIN_END
268       "AND (transition & ?) NOT IN (?, ?, ?) "  // NO SUBFRAME or
269                                                 // KEYWORD_GENERATED
270       "ORDER BY visit_time DESC"));
271   statement.BindInt64(0, url_id);
272   statement.BindInt64(1, options.EffectiveBeginTime());
273   statement.BindInt64(2, options.EffectiveEndTime());
274   statement.BindInt(3, content::PAGE_TRANSITION_CHAIN_END);
275   statement.BindInt(4, content::PAGE_TRANSITION_CORE_MASK);
276   statement.BindInt(5, content::PAGE_TRANSITION_AUTO_SUBFRAME);
277   statement.BindInt(6, content::PAGE_TRANSITION_MANUAL_SUBFRAME);
278   statement.BindInt(7, content::PAGE_TRANSITION_KEYWORD_GENERATED);
279 
280   return FillVisitVectorWithOptions(statement, options, visits);
281 }
282 
GetVisitsForTimes(const std::vector<base::Time> & times,VisitVector * visits)283 bool VisitDatabase::GetVisitsForTimes(const std::vector<base::Time>& times,
284                                       VisitVector* visits) {
285   visits->clear();
286 
287   for (std::vector<base::Time>::const_iterator it = times.begin();
288        it != times.end(); ++it) {
289     sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
290         "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
291         "WHERE visit_time == ?"));
292 
293     statement.BindInt64(0, it->ToInternalValue());
294 
295     if (!FillVisitVector(statement, visits))
296       return false;
297   }
298   return true;
299 }
300 
GetAllVisitsInRange(base::Time begin_time,base::Time end_time,int max_results,VisitVector * visits)301 bool VisitDatabase::GetAllVisitsInRange(base::Time begin_time,
302                                         base::Time end_time,
303                                         int max_results,
304                                         VisitVector* visits) {
305   visits->clear();
306 
307   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
308       "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
309       "WHERE visit_time >= ? AND visit_time < ?"
310       "ORDER BY visit_time LIMIT ?"));
311 
312   // See GetVisibleVisitsInRange for more info on how these times are bound.
313   int64 end = end_time.ToInternalValue();
314   statement.BindInt64(0, begin_time.ToInternalValue());
315   statement.BindInt64(1, end ? end : std::numeric_limits<int64>::max());
316   statement.BindInt64(2,
317       max_results ? max_results : std::numeric_limits<int64>::max());
318 
319   return FillVisitVector(statement, visits);
320 }
321 
GetVisitsInRangeForTransition(base::Time begin_time,base::Time end_time,int max_results,content::PageTransition transition,VisitVector * visits)322 bool VisitDatabase::GetVisitsInRangeForTransition(
323     base::Time begin_time,
324     base::Time end_time,
325     int max_results,
326     content::PageTransition transition,
327     VisitVector* visits) {
328   DCHECK(visits);
329   visits->clear();
330 
331   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
332       "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
333       "WHERE visit_time >= ? AND visit_time < ? "
334       "AND (transition & ?) == ?"
335       "ORDER BY visit_time LIMIT ?"));
336 
337   // See GetVisibleVisitsInRange for more info on how these times are bound.
338   int64 end = end_time.ToInternalValue();
339   statement.BindInt64(0, begin_time.ToInternalValue());
340   statement.BindInt64(1, end ? end : std::numeric_limits<int64>::max());
341   statement.BindInt(2, content::PAGE_TRANSITION_CORE_MASK);
342   statement.BindInt(3, transition);
343   statement.BindInt64(4,
344       max_results ? max_results : std::numeric_limits<int64>::max());
345 
346   return FillVisitVector(statement, visits);
347 }
348 
GetVisibleVisitsInRange(const QueryOptions & options,VisitVector * visits)349 bool VisitDatabase::GetVisibleVisitsInRange(const QueryOptions& options,
350                                             VisitVector* visits) {
351   visits->clear();
352   // The visit_time values can be duplicated in a redirect chain, so we sort
353   // by id too, to ensure a consistent ordering just in case.
354   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
355       "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
356       "WHERE visit_time >= ? AND visit_time < ? "
357       "AND (transition & ?) != 0 "  // CHAIN_END
358       "AND (transition & ?) NOT IN (?, ?, ?) "  // NO SUBFRAME or
359                                                 // KEYWORD_GENERATED
360       "ORDER BY visit_time DESC, id DESC"));
361 
362   statement.BindInt64(0, options.EffectiveBeginTime());
363   statement.BindInt64(1, options.EffectiveEndTime());
364   statement.BindInt(2, content::PAGE_TRANSITION_CHAIN_END);
365   statement.BindInt(3, content::PAGE_TRANSITION_CORE_MASK);
366   statement.BindInt(4, content::PAGE_TRANSITION_AUTO_SUBFRAME);
367   statement.BindInt(5, content::PAGE_TRANSITION_MANUAL_SUBFRAME);
368   statement.BindInt(6, content::PAGE_TRANSITION_KEYWORD_GENERATED);
369 
370   return FillVisitVectorWithOptions(statement, options, visits);
371 }
372 
GetDirectVisitsDuringTimes(const VisitFilter & time_filter,int max_results,VisitVector * visits)373 void VisitDatabase::GetDirectVisitsDuringTimes(const VisitFilter& time_filter,
374                                                 int max_results,
375                                                 VisitVector* visits) {
376   visits->clear();
377   if (max_results)
378     visits->reserve(max_results);
379   for (VisitFilter::TimeVector::const_iterator it = time_filter.times().begin();
380        it != time_filter.times().end(); ++it) {
381     sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
382         "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
383         "WHERE visit_time >= ? AND visit_time < ? "
384         "AND (transition & ?) != 0 "  // CHAIN_START
385         "AND (transition & ?) IN (?, ?) "  // TYPED or AUTO_BOOKMARK only
386         "ORDER BY visit_time DESC, id DESC"));
387 
388     statement.BindInt64(0, it->first.ToInternalValue());
389     statement.BindInt64(1, it->second.ToInternalValue());
390     statement.BindInt(2, content::PAGE_TRANSITION_CHAIN_START);
391     statement.BindInt(3, content::PAGE_TRANSITION_CORE_MASK);
392     statement.BindInt(4, content::PAGE_TRANSITION_TYPED);
393     statement.BindInt(5, content::PAGE_TRANSITION_AUTO_BOOKMARK);
394 
395     while (statement.Step()) {
396       VisitRow visit;
397       FillVisitRow(statement, &visit);
398       visits->push_back(visit);
399 
400       if (max_results > 0 && static_cast<int>(visits->size()) >= max_results)
401         return;
402     }
403   }
404 }
405 
GetMostRecentVisitForURL(URLID url_id,VisitRow * visit_row)406 VisitID VisitDatabase::GetMostRecentVisitForURL(URLID url_id,
407                                                 VisitRow* visit_row) {
408   // The visit_time values can be duplicated in a redirect chain, so we sort
409   // by id too, to ensure a consistent ordering just in case.
410   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
411       "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
412       "WHERE url=? "
413       "ORDER BY visit_time DESC, id DESC "
414       "LIMIT 1"));
415   statement.BindInt64(0, url_id);
416   if (!statement.Step())
417     return 0;  // No visits for this URL.
418 
419   if (visit_row) {
420     FillVisitRow(statement, visit_row);
421     return visit_row->visit_id;
422   }
423   return statement.ColumnInt64(0);
424 }
425 
GetMostRecentVisitsForURL(URLID url_id,int max_results,VisitVector * visits)426 bool VisitDatabase::GetMostRecentVisitsForURL(URLID url_id,
427                                               int max_results,
428                                               VisitVector* visits) {
429   visits->clear();
430 
431   // The visit_time values can be duplicated in a redirect chain, so we sort
432   // by id too, to ensure a consistent ordering just in case.
433   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
434       "SELECT" HISTORY_VISIT_ROW_FIELDS
435       "FROM visits "
436       "WHERE url=? "
437       "ORDER BY visit_time DESC, id DESC "
438       "LIMIT ?"));
439   statement.BindInt64(0, url_id);
440   statement.BindInt(1, max_results);
441 
442   return FillVisitVector(statement, visits);
443 }
444 
GetRedirectFromVisit(VisitID from_visit,VisitID * to_visit,GURL * to_url)445 bool VisitDatabase::GetRedirectFromVisit(VisitID from_visit,
446                                          VisitID* to_visit,
447                                          GURL* to_url) {
448   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
449       "SELECT v.id,u.url "
450       "FROM visits v JOIN urls u ON v.url = u.id "
451       "WHERE v.from_visit = ? "
452       "AND (v.transition & ?) != 0"));  // IS_REDIRECT_MASK
453   statement.BindInt64(0, from_visit);
454   statement.BindInt(1, content::PAGE_TRANSITION_IS_REDIRECT_MASK);
455 
456   if (!statement.Step())
457     return false;  // No redirect from this visit. (Or SQL error)
458   if (to_visit)
459     *to_visit = statement.ColumnInt64(0);
460   if (to_url)
461     *to_url = GURL(statement.ColumnString(1));
462   return true;
463 }
464 
GetRedirectToVisit(VisitID to_visit,VisitID * from_visit,GURL * from_url)465 bool VisitDatabase::GetRedirectToVisit(VisitID to_visit,
466                                        VisitID* from_visit,
467                                        GURL* from_url) {
468   VisitRow row;
469   if (!GetRowForVisit(to_visit, &row))
470     return false;
471 
472   if (from_visit)
473     *from_visit = row.referring_visit;
474 
475   if (from_url) {
476     sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
477         "SELECT u.url "
478         "FROM visits v JOIN urls u ON v.url = u.id "
479         "WHERE v.id = ?"));
480     statement.BindInt64(0, row.referring_visit);
481 
482     if (!statement.Step())
483       return false;
484 
485     *from_url = GURL(statement.ColumnString(0));
486   }
487   return true;
488 }
489 
GetVisibleVisitCountToHost(const GURL & url,int * count,base::Time * first_visit)490 bool VisitDatabase::GetVisibleVisitCountToHost(const GURL& url,
491                                                int* count,
492                                                base::Time* first_visit) {
493   if (!url.SchemeIs(url::kHttpScheme) &&
494       !url.SchemeIs(url::kHttpsScheme))
495     return false;
496 
497   // We need to search for URLs with a matching host/port. One way to query for
498   // this is to use the LIKE operator, eg 'url LIKE http://google.com/%'. This
499   // is inefficient though in that it doesn't use the index and each entry must
500   // be visited. The same query can be executed by using >= and < operator.
501   // The query becomes:
502   // 'url >= http://google.com/' and url < http://google.com0'.
503   // 0 is used as it is one character greater than '/'.
504   const std::string host_query_min = url.GetOrigin().spec();
505   if (host_query_min.empty())
506     return false;
507 
508   // We also want to restrict ourselves to main frame navigations that are not
509   // in the middle of redirect chains, hence the transition checks.
510   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
511       "SELECT MIN(v.visit_time), COUNT(*) "
512       "FROM visits v INNER JOIN urls u ON v.url = u.id "
513       "WHERE u.url >= ? AND u.url < ? "
514       "AND (transition & ?) != 0 "
515       "AND (transition & ?) NOT IN (?, ?, ?)"));
516   statement.BindString(0, host_query_min);
517   statement.BindString(1,
518       host_query_min.substr(0, host_query_min.size() - 1) + '0');
519   statement.BindInt(2, content::PAGE_TRANSITION_CHAIN_END);
520   statement.BindInt(3, content::PAGE_TRANSITION_CORE_MASK);
521   statement.BindInt(4, content::PAGE_TRANSITION_AUTO_SUBFRAME);
522   statement.BindInt(5, content::PAGE_TRANSITION_MANUAL_SUBFRAME);
523   statement.BindInt(6, content::PAGE_TRANSITION_KEYWORD_GENERATED);
524 
525   if (!statement.Step()) {
526     // We've never been to this page before.
527     *count = 0;
528     return true;
529   }
530 
531   if (!statement.Succeeded())
532     return false;
533 
534   *first_visit = base::Time::FromInternalValue(statement.ColumnInt64(0));
535   *count = statement.ColumnInt(1);
536   return true;
537 }
538 
GetStartDate(base::Time * first_visit)539 bool VisitDatabase::GetStartDate(base::Time* first_visit) {
540   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
541       "SELECT MIN(visit_time) FROM visits WHERE visit_time != 0"));
542   if (!statement.Step() || statement.ColumnInt64(0) == 0) {
543     *first_visit = base::Time::Now();
544     return false;
545   }
546   *first_visit = base::Time::FromInternalValue(statement.ColumnInt64(0));
547   return true;
548 }
549 
GetVisitsSource(const VisitVector & visits,VisitSourceMap * sources)550 void VisitDatabase::GetVisitsSource(const VisitVector& visits,
551                                     VisitSourceMap* sources) {
552   DCHECK(sources);
553   sources->clear();
554 
555   // We query the source in batch. Here defines the batch size.
556   const size_t batch_size = 500;
557   size_t visits_size = visits.size();
558 
559   size_t start_index = 0, end_index = 0;
560   while (end_index < visits_size) {
561     start_index = end_index;
562     end_index = end_index + batch_size < visits_size ? end_index + batch_size
563                                                      : visits_size;
564 
565     // Compose the sql statement with a list of ids.
566     std::string sql = "SELECT id,source FROM visit_source ";
567     sql.append("WHERE id IN (");
568     // Append all the ids in the statement.
569     for (size_t j = start_index; j < end_index; j++) {
570       if (j != start_index)
571         sql.push_back(',');
572       sql.append(base::Int64ToString(visits[j].visit_id));
573     }
574     sql.append(") ORDER BY id");
575     sql::Statement statement(GetDB().GetUniqueStatement(sql.c_str()));
576 
577     // Get the source entries out of the query result.
578     while (statement.Step()) {
579       std::pair<VisitID, VisitSource> source_entry(statement.ColumnInt64(0),
580           static_cast<VisitSource>(statement.ColumnInt(1)));
581       sources->insert(source_entry);
582     }
583   }
584 }
585 
MigrateVisitsWithoutDuration()586 bool VisitDatabase::MigrateVisitsWithoutDuration() {
587   if (!GetDB().DoesTableExist("visits")) {
588     NOTREACHED() << " Visits table should exist before migration";
589     return false;
590   }
591 
592   if (!GetDB().DoesColumnExist("visits", "visit_duration")) {
593     // Old versions don't have the visit_duration column, we modify the table
594     // to add that field.
595     if (!GetDB().Execute("ALTER TABLE visits "
596         "ADD COLUMN visit_duration INTEGER DEFAULT 0 NOT NULL"))
597       return false;
598   }
599   return true;
600 }
601 
GetBriefVisitInfoOfMostRecentVisits(int max_visits,std::vector<BriefVisitInfo> * result_vector)602 void VisitDatabase::GetBriefVisitInfoOfMostRecentVisits(
603     int max_visits,
604     std::vector<BriefVisitInfo>* result_vector) {
605   result_vector->clear();
606 
607   sql::Statement statement(GetDB().GetUniqueStatement(
608       "SELECT url,visit_time,transition FROM visits "
609       "ORDER BY id DESC LIMIT ?"));
610 
611   statement.BindInt64(0, max_visits);
612 
613   if (!statement.is_valid())
614     return;
615 
616   while (statement.Step()) {
617     BriefVisitInfo info;
618     info.url_id = statement.ColumnInt64(0);
619     info.time = base::Time::FromInternalValue(statement.ColumnInt64(1));
620     info.transition = content::PageTransitionFromInt(statement.ColumnInt(2));
621     result_vector->push_back(info);
622   }
623 }
624 
625 }  // namespace history
626