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/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 
     22 VisitDatabase::VisitDatabase() {
     23 }
     24 
     25 VisitDatabase::~VisitDatabase() {
     26 }
     27 
     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 
     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
     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
     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
    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 
    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 
    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 
    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 
    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 
    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 
    258 bool VisitDatabase::GetVisibleVisitsForURL(URLID url_id,
    259                                            const QueryOptions& options,
    260                                            VisitVector* visits) {
    261   visits->clear();
    262 
    263   if (options.REMOVE_ALL_DUPLICATES) {
    264     VisitRow visit_row;
    265     VisitID visit_id = GetMostRecentVisitForURL(url_id, &visit_row);
    266     if (visit_id && options.EffectiveMaxCount() != 0) {
    267       visits->push_back(visit_row);
    268     }
    269     return options.EffectiveMaxCount() == 0 && visit_id;
    270   } else {
    271     sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    272         "SELECT" HISTORY_VISIT_ROW_FIELDS
    273         "FROM visits "
    274         "WHERE url=? AND visit_time >= ? AND visit_time < ? "
    275         "AND (transition & ?) != 0 "  // CHAIN_END
    276         "AND (transition & ?) NOT IN (?, ?, ?) "  // NO SUBFRAME or
    277                                                   // KEYWORD_GENERATED
    278         "ORDER BY visit_time DESC"));
    279     statement.BindInt64(0, url_id);
    280     statement.BindInt64(1, options.EffectiveBeginTime());
    281     statement.BindInt64(2, options.EffectiveEndTime());
    282     statement.BindInt(3, content::PAGE_TRANSITION_CHAIN_END);
    283     statement.BindInt(4, content::PAGE_TRANSITION_CORE_MASK);
    284     statement.BindInt(5, content::PAGE_TRANSITION_AUTO_SUBFRAME);
    285     statement.BindInt(6, content::PAGE_TRANSITION_MANUAL_SUBFRAME);
    286     statement.BindInt(7, content::PAGE_TRANSITION_KEYWORD_GENERATED);
    287 
    288     return FillVisitVectorWithOptions(statement, options, visits);
    289   }
    290 }
    291 
    292 bool VisitDatabase::GetVisitsForTimes(const std::vector<base::Time>& times,
    293                                       VisitVector* visits) {
    294   visits->clear();
    295 
    296   for (std::vector<base::Time>::const_iterator it = times.begin();
    297        it != times.end(); ++it) {
    298     sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    299         "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
    300         "WHERE visit_time == ?"));
    301 
    302     statement.BindInt64(0, it->ToInternalValue());
    303 
    304     if (!FillVisitVector(statement, visits))
    305       return false;
    306   }
    307   return true;
    308 }
    309 
    310 bool VisitDatabase::GetAllVisitsInRange(base::Time begin_time,
    311                                         base::Time end_time,
    312                                         int max_results,
    313                                         VisitVector* visits) {
    314   visits->clear();
    315 
    316   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    317       "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
    318       "WHERE visit_time >= ? AND visit_time < ?"
    319       "ORDER BY visit_time LIMIT ?"));
    320 
    321   // See GetVisibleVisitsInRange for more info on how these times are bound.
    322   int64 end = end_time.ToInternalValue();
    323   statement.BindInt64(0, begin_time.ToInternalValue());
    324   statement.BindInt64(1, end ? end : std::numeric_limits<int64>::max());
    325   statement.BindInt64(2,
    326       max_results ? max_results : std::numeric_limits<int64>::max());
    327 
    328   return FillVisitVector(statement, visits);
    329 }
    330 
    331 bool VisitDatabase::GetVisitsInRangeForTransition(
    332     base::Time begin_time,
    333     base::Time end_time,
    334     int max_results,
    335     content::PageTransition transition,
    336     VisitVector* visits) {
    337   DCHECK(visits);
    338   visits->clear();
    339 
    340   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    341       "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
    342       "WHERE visit_time >= ? AND visit_time < ? "
    343       "AND (transition & ?) == ?"
    344       "ORDER BY visit_time LIMIT ?"));
    345 
    346   // See GetVisibleVisitsInRange for more info on how these times are bound.
    347   int64 end = end_time.ToInternalValue();
    348   statement.BindInt64(0, begin_time.ToInternalValue());
    349   statement.BindInt64(1, end ? end : std::numeric_limits<int64>::max());
    350   statement.BindInt(2, content::PAGE_TRANSITION_CORE_MASK);
    351   statement.BindInt(3, transition);
    352   statement.BindInt64(4,
    353       max_results ? max_results : std::numeric_limits<int64>::max());
    354 
    355   return FillVisitVector(statement, visits);
    356 }
    357 
    358 bool VisitDatabase::GetVisibleVisitsInRange(const QueryOptions& options,
    359                                             VisitVector* visits) {
    360   visits->clear();
    361   // The visit_time values can be duplicated in a redirect chain, so we sort
    362   // by id too, to ensure a consistent ordering just in case.
    363   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    364       "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
    365       "WHERE visit_time >= ? AND visit_time < ? "
    366       "AND (transition & ?) != 0 "  // CHAIN_END
    367       "AND (transition & ?) NOT IN (?, ?, ?) "  // NO SUBFRAME or
    368                                                 // KEYWORD_GENERATED
    369       "ORDER BY visit_time DESC, id DESC"));
    370 
    371   statement.BindInt64(0, options.EffectiveBeginTime());
    372   statement.BindInt64(1, options.EffectiveEndTime());
    373   statement.BindInt(2, content::PAGE_TRANSITION_CHAIN_END);
    374   statement.BindInt(3, content::PAGE_TRANSITION_CORE_MASK);
    375   statement.BindInt(4, content::PAGE_TRANSITION_AUTO_SUBFRAME);
    376   statement.BindInt(5, content::PAGE_TRANSITION_MANUAL_SUBFRAME);
    377   statement.BindInt(6, content::PAGE_TRANSITION_KEYWORD_GENERATED);
    378 
    379   return FillVisitVectorWithOptions(statement, options, visits);
    380 }
    381 
    382 void VisitDatabase::GetDirectVisitsDuringTimes(const VisitFilter& time_filter,
    383                                                 int max_results,
    384                                                 VisitVector* visits) {
    385   visits->clear();
    386   if (max_results)
    387     visits->reserve(max_results);
    388   for (VisitFilter::TimeVector::const_iterator it = time_filter.times().begin();
    389        it != time_filter.times().end(); ++it) {
    390     sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    391         "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
    392         "WHERE visit_time >= ? AND visit_time < ? "
    393         "AND (transition & ?) != 0 "  // CHAIN_START
    394         "AND (transition & ?) IN (?, ?) "  // TYPED or AUTO_BOOKMARK only
    395         "ORDER BY visit_time DESC, id DESC"));
    396 
    397     statement.BindInt64(0, it->first.ToInternalValue());
    398     statement.BindInt64(1, it->second.ToInternalValue());
    399     statement.BindInt(2, content::PAGE_TRANSITION_CHAIN_START);
    400     statement.BindInt(3, content::PAGE_TRANSITION_CORE_MASK);
    401     statement.BindInt(4, content::PAGE_TRANSITION_TYPED);
    402     statement.BindInt(5, content::PAGE_TRANSITION_AUTO_BOOKMARK);
    403 
    404     while (statement.Step()) {
    405       VisitRow visit;
    406       FillVisitRow(statement, &visit);
    407       visits->push_back(visit);
    408 
    409       if (max_results > 0 && static_cast<int>(visits->size()) >= max_results)
    410         return;
    411     }
    412   }
    413 }
    414 
    415 VisitID VisitDatabase::GetMostRecentVisitForURL(URLID url_id,
    416                                                 VisitRow* visit_row) {
    417   // The visit_time values can be duplicated in a redirect chain, so we sort
    418   // by id too, to ensure a consistent ordering just in case.
    419   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    420       "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits "
    421       "WHERE url=? "
    422       "ORDER BY visit_time DESC, id DESC "
    423       "LIMIT 1"));
    424   statement.BindInt64(0, url_id);
    425   if (!statement.Step())
    426     return 0;  // No visits for this URL.
    427 
    428   if (visit_row) {
    429     FillVisitRow(statement, visit_row);
    430     return visit_row->visit_id;
    431   }
    432   return statement.ColumnInt64(0);
    433 }
    434 
    435 bool VisitDatabase::GetMostRecentVisitsForURL(URLID url_id,
    436                                               int max_results,
    437                                               VisitVector* visits) {
    438   visits->clear();
    439 
    440   // The visit_time values can be duplicated in a redirect chain, so we sort
    441   // by id too, to ensure a consistent ordering just in case.
    442   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    443       "SELECT" HISTORY_VISIT_ROW_FIELDS
    444       "FROM visits "
    445       "WHERE url=? "
    446       "ORDER BY visit_time DESC, id DESC "
    447       "LIMIT ?"));
    448   statement.BindInt64(0, url_id);
    449   statement.BindInt(1, max_results);
    450 
    451   return FillVisitVector(statement, visits);
    452 }
    453 
    454 bool VisitDatabase::GetRedirectFromVisit(VisitID from_visit,
    455                                          VisitID* to_visit,
    456                                          GURL* to_url) {
    457   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    458       "SELECT v.id,u.url "
    459       "FROM visits v JOIN urls u ON v.url = u.id "
    460       "WHERE v.from_visit = ? "
    461       "AND (v.transition & ?) != 0"));  // IS_REDIRECT_MASK
    462   statement.BindInt64(0, from_visit);
    463   statement.BindInt(1, content::PAGE_TRANSITION_IS_REDIRECT_MASK);
    464 
    465   if (!statement.Step())
    466     return false;  // No redirect from this visit. (Or SQL error)
    467   if (to_visit)
    468     *to_visit = statement.ColumnInt64(0);
    469   if (to_url)
    470     *to_url = GURL(statement.ColumnString(1));
    471   return true;
    472 }
    473 
    474 bool VisitDatabase::GetRedirectToVisit(VisitID to_visit,
    475                                        VisitID* from_visit,
    476                                        GURL* from_url) {
    477   VisitRow row;
    478   if (!GetRowForVisit(to_visit, &row))
    479     return false;
    480 
    481   if (from_visit)
    482     *from_visit = row.referring_visit;
    483 
    484   if (from_url) {
    485     sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    486         "SELECT u.url "
    487         "FROM visits v JOIN urls u ON v.url = u.id "
    488         "WHERE v.id = ?"));
    489     statement.BindInt64(0, row.referring_visit);
    490 
    491     if (!statement.Step())
    492       return false;
    493 
    494     *from_url = GURL(statement.ColumnString(0));
    495   }
    496   return true;
    497 }
    498 
    499 bool VisitDatabase::GetVisibleVisitCountToHost(const GURL& url,
    500                                                int* count,
    501                                                base::Time* first_visit) {
    502   if (!url.SchemeIs(content::kHttpScheme) &&
    503       !url.SchemeIs(content::kHttpsScheme))
    504     return false;
    505 
    506   // We need to search for URLs with a matching host/port. One way to query for
    507   // this is to use the LIKE operator, eg 'url LIKE http://google.com/%'. This
    508   // is inefficient though in that it doesn't use the index and each entry must
    509   // be visited. The same query can be executed by using >= and < operator.
    510   // The query becomes:
    511   // 'url >= http://google.com/' and url < http://google.com0'.
    512   // 0 is used as it is one character greater than '/'.
    513   const std::string host_query_min = url.GetOrigin().spec();
    514   if (host_query_min.empty())
    515     return false;
    516 
    517   // We also want to restrict ourselves to main frame navigations that are not
    518   // in the middle of redirect chains, hence the transition checks.
    519   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    520       "SELECT MIN(v.visit_time), COUNT(*) "
    521       "FROM visits v INNER JOIN urls u ON v.url = u.id "
    522       "WHERE u.url >= ? AND u.url < ? "
    523       "AND (transition & ?) != 0 "
    524       "AND (transition & ?) NOT IN (?, ?, ?)"));
    525   statement.BindString(0, host_query_min);
    526   statement.BindString(1,
    527       host_query_min.substr(0, host_query_min.size() - 1) + '0');
    528   statement.BindInt(2, content::PAGE_TRANSITION_CHAIN_END);
    529   statement.BindInt(3, content::PAGE_TRANSITION_CORE_MASK);
    530   statement.BindInt(4, content::PAGE_TRANSITION_AUTO_SUBFRAME);
    531   statement.BindInt(5, content::PAGE_TRANSITION_MANUAL_SUBFRAME);
    532   statement.BindInt(6, content::PAGE_TRANSITION_KEYWORD_GENERATED);
    533 
    534   if (!statement.Step()) {
    535     // We've never been to this page before.
    536     *count = 0;
    537     return true;
    538   }
    539 
    540   if (!statement.Succeeded())
    541     return false;
    542 
    543   *first_visit = base::Time::FromInternalValue(statement.ColumnInt64(0));
    544   *count = statement.ColumnInt(1);
    545   return true;
    546 }
    547 
    548 bool VisitDatabase::GetStartDate(base::Time* first_visit) {
    549   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    550       "SELECT MIN(visit_time) FROM visits WHERE visit_time != 0"));
    551   if (!statement.Step() || statement.ColumnInt64(0) == 0) {
    552     *first_visit = base::Time::Now();
    553     return false;
    554   }
    555   *first_visit = base::Time::FromInternalValue(statement.ColumnInt64(0));
    556   return true;
    557 }
    558 
    559 void VisitDatabase::GetVisitsSource(const VisitVector& visits,
    560                                     VisitSourceMap* sources) {
    561   DCHECK(sources);
    562   sources->clear();
    563 
    564   // We query the source in batch. Here defines the batch size.
    565   const size_t batch_size = 500;
    566   size_t visits_size = visits.size();
    567 
    568   size_t start_index = 0, end_index = 0;
    569   while (end_index < visits_size) {
    570     start_index = end_index;
    571     end_index = end_index + batch_size < visits_size ? end_index + batch_size
    572                                                      : visits_size;
    573 
    574     // Compose the sql statement with a list of ids.
    575     std::string sql = "SELECT id,source FROM visit_source ";
    576     sql.append("WHERE id IN (");
    577     // Append all the ids in the statement.
    578     for (size_t j = start_index; j < end_index; j++) {
    579       if (j != start_index)
    580         sql.push_back(',');
    581       sql.append(base::Int64ToString(visits[j].visit_id));
    582     }
    583     sql.append(") ORDER BY id");
    584     sql::Statement statement(GetDB().GetUniqueStatement(sql.c_str()));
    585 
    586     // Get the source entries out of the query result.
    587     while (statement.Step()) {
    588       std::pair<VisitID, VisitSource> source_entry(statement.ColumnInt64(0),
    589           static_cast<VisitSource>(statement.ColumnInt(1)));
    590       sources->insert(source_entry);
    591     }
    592   }
    593 }
    594 
    595 bool VisitDatabase::MigrateVisitsWithoutDuration() {
    596   if (!GetDB().DoesTableExist("visits")) {
    597     NOTREACHED() << " Visits table should exist before migration";
    598     return false;
    599   }
    600 
    601   if (!GetDB().DoesColumnExist("visits", "visit_duration")) {
    602     // Old versions don't have the visit_duration column, we modify the table
    603     // to add that field.
    604     if (!GetDB().Execute("ALTER TABLE visits "
    605         "ADD COLUMN visit_duration INTEGER DEFAULT 0 NOT NULL"))
    606       return false;
    607   }
    608   return true;
    609 }
    610 
    611 void VisitDatabase::GetBriefVisitInfoOfMostRecentVisits(
    612     int max_visits,
    613     std::vector<BriefVisitInfo>* result_vector) {
    614   result_vector->clear();
    615 
    616   sql::Statement statement(GetDB().GetUniqueStatement(
    617       "SELECT url,visit_time,transition FROM visits "
    618       "ORDER BY id DESC LIMIT ?"));
    619 
    620   statement.BindInt64(0, max_visits);
    621 
    622   if (!statement.is_valid())
    623     return;
    624 
    625   while (statement.Step()) {
    626     BriefVisitInfo info;
    627     info.url_id = statement.ColumnInt64(0);
    628     info.time = base::Time::FromInternalValue(statement.ColumnInt64(1));
    629     info.transition = content::PageTransitionFromInt(statement.ColumnInt(2));
    630     result_vector->push_back(info);
    631   }
    632 }
    633 
    634 }  // namespace history
    635