Home | History | Annotate | Download | only in browser
      1 // Copyright 2014 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 "components/history/core/browser/url_database.h"
      6 
      7 #include <limits>
      8 #include <string>
      9 #include <vector>
     10 
     11 #include "base/i18n/case_conversion.h"
     12 #include "base/memory/scoped_vector.h"
     13 #include "base/strings/utf_string_conversions.h"
     14 #include "components/history/core/browser/keyword_search_term.h"
     15 #include "net/base/net_util.h"
     16 #include "sql/statement.h"
     17 #include "url/gurl.h"
     18 
     19 namespace history {
     20 
     21 const char URLDatabase::kURLRowFields[] = HISTORY_URL_ROW_FIELDS;
     22 const int URLDatabase::kNumURLRowFields = 9;
     23 
     24 URLDatabase::URLEnumeratorBase::URLEnumeratorBase()
     25     : initialized_(false) {
     26 }
     27 
     28 URLDatabase::URLEnumeratorBase::~URLEnumeratorBase() {
     29 }
     30 
     31 URLDatabase::URLEnumerator::URLEnumerator() {
     32 }
     33 
     34 bool URLDatabase::URLEnumerator::GetNextURL(URLRow* r) {
     35   if (statement_.Step()) {
     36     FillURLRow(statement_, r);
     37     return true;
     38   }
     39   return false;
     40 }
     41 
     42 URLDatabase::URLDatabase()
     43     : has_keyword_search_terms_(false) {
     44 }
     45 
     46 URLDatabase::~URLDatabase() {
     47 }
     48 
     49 // static
     50 std::string URLDatabase::GURLToDatabaseURL(const GURL& gurl) {
     51   // TODO(brettw): do something fancy here with encoding, etc.
     52 
     53   // Strip username and password from URL before sending to DB.
     54   GURL::Replacements replacements;
     55   replacements.ClearUsername();
     56   replacements.ClearPassword();
     57 
     58   return (gurl.ReplaceComponents(replacements)).spec();
     59 }
     60 
     61 // Convenience to fill a history::URLRow. Must be in sync with the fields in
     62 // kURLRowFields.
     63 void URLDatabase::FillURLRow(sql::Statement& s, history::URLRow* i) {
     64   DCHECK(i);
     65   i->id_ = s.ColumnInt64(0);
     66   i->url_ = GURL(s.ColumnString(1));
     67   i->title_ = s.ColumnString16(2);
     68   i->visit_count_ = s.ColumnInt(3);
     69   i->typed_count_ = s.ColumnInt(4);
     70   i->last_visit_ = base::Time::FromInternalValue(s.ColumnInt64(5));
     71   i->hidden_ = s.ColumnInt(6) != 0;
     72 }
     73 
     74 bool URLDatabase::GetURLRow(URLID url_id, URLRow* info) {
     75   // TODO(brettw) We need check for empty URLs to handle the case where
     76   // there are old URLs in the database that are empty that got in before
     77   // we added any checks. We should eventually be able to remove it
     78   // when all inputs are using GURL (which prohibit empty input).
     79   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
     80       "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE id=?"));
     81   statement.BindInt64(0, url_id);
     82 
     83   if (statement.Step()) {
     84     FillURLRow(statement, info);
     85     return true;
     86   }
     87   return false;
     88 }
     89 
     90 bool URLDatabase::GetAllTypedUrls(URLRows* urls) {
     91   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
     92       "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE typed_count > 0"));
     93 
     94   while (statement.Step()) {
     95     URLRow info;
     96     FillURLRow(statement, &info);
     97     urls->push_back(info);
     98   }
     99   return true;
    100 }
    101 
    102 URLID URLDatabase::GetRowForURL(const GURL& url, history::URLRow* info) {
    103   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    104       "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE url=?"));
    105   std::string url_string = GURLToDatabaseURL(url);
    106   statement.BindString(0, url_string);
    107 
    108   if (!statement.Step())
    109     return 0;  // no data
    110 
    111   if (info)
    112     FillURLRow(statement, info);
    113   return statement.ColumnInt64(0);
    114 }
    115 
    116 bool URLDatabase::UpdateURLRow(URLID url_id,
    117                                const history::URLRow& info) {
    118   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    119       "UPDATE urls SET title=?,visit_count=?,typed_count=?,last_visit_time=?,"
    120         "hidden=?"
    121       "WHERE id=?"));
    122   statement.BindString16(0, info.title());
    123   statement.BindInt(1, info.visit_count());
    124   statement.BindInt(2, info.typed_count());
    125   statement.BindInt64(3, info.last_visit().ToInternalValue());
    126   statement.BindInt(4, info.hidden() ? 1 : 0);
    127   statement.BindInt64(5, url_id);
    128 
    129   return statement.Run() && GetDB().GetLastChangeCount() > 0;
    130 }
    131 
    132 URLID URLDatabase::AddURLInternal(const history::URLRow& info,
    133                                   bool is_temporary) {
    134   // This function is used to insert into two different tables, so we have to
    135   // do some shuffling. Unfortinately, we can't use the macro
    136   // HISTORY_URL_ROW_FIELDS because that specifies the table name which is
    137   // invalid in the insert syntax.
    138   #define ADDURL_COMMON_SUFFIX \
    139       " (url, title, visit_count, typed_count, "\
    140       "last_visit_time, hidden) "\
    141       "VALUES (?,?,?,?,?,?)"
    142   const char* statement_name;
    143   const char* statement_sql;
    144   if (is_temporary) {
    145     statement_name = "AddURLTemporary";
    146     statement_sql = "INSERT INTO temp_urls" ADDURL_COMMON_SUFFIX;
    147   } else {
    148     statement_name = "AddURL";
    149     statement_sql = "INSERT INTO urls" ADDURL_COMMON_SUFFIX;
    150   }
    151   #undef ADDURL_COMMON_SUFFIX
    152 
    153   sql::Statement statement(GetDB().GetCachedStatement(
    154       sql::StatementID(statement_name), statement_sql));
    155   statement.BindString(0, GURLToDatabaseURL(info.url()));
    156   statement.BindString16(1, info.title());
    157   statement.BindInt(2, info.visit_count());
    158   statement.BindInt(3, info.typed_count());
    159   statement.BindInt64(4, info.last_visit().ToInternalValue());
    160   statement.BindInt(5, info.hidden() ? 1 : 0);
    161 
    162   if (!statement.Run()) {
    163     VLOG(0) << "Failed to add url " << info.url().possibly_invalid_spec()
    164             << " to table history.urls.";
    165     return 0;
    166   }
    167   return GetDB().GetLastInsertRowId();
    168 }
    169 
    170 bool URLDatabase::InsertOrUpdateURLRowByID(const history::URLRow& info) {
    171   // SQLite does not support INSERT OR UPDATE, however, it does have INSERT OR
    172   // REPLACE, which is feasible to use, because of the following.
    173   //  * Before INSERTing, REPLACE will delete all pre-existing rows that cause
    174   //    constraint violations. Here, we only have a PRIMARY KEY constraint, so
    175   //    the only row that might get deleted is an old one with the same ID.
    176   //  * Another difference between the two flavors is that the latter actually
    177   //    deletes the old row, and thus the old values are lost in columns which
    178   //    are not explicitly assigned new values. This is not an issue, however,
    179   //    as we assign values to all columns.
    180   //  * When rows are deleted due to constraint violations, the delete triggers
    181   //    may not be invoked. As of now, we do not have any delete triggers.
    182   // For more details, see: http://www.sqlite.org/lang_conflict.html.
    183   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    184       "INSERT OR REPLACE INTO urls "
    185       "(id, url, title, visit_count, typed_count, last_visit_time, hidden) "
    186       "VALUES (?, ?, ?, ?, ?, ?, ?)"));
    187 
    188   statement.BindInt64(0, info.id());
    189   statement.BindString(1, GURLToDatabaseURL(info.url()));
    190   statement.BindString16(2, info.title());
    191   statement.BindInt(3, info.visit_count());
    192   statement.BindInt(4, info.typed_count());
    193   statement.BindInt64(5, info.last_visit().ToInternalValue());
    194   statement.BindInt(6, info.hidden() ? 1 : 0);
    195 
    196   return statement.Run();
    197 }
    198 
    199 bool URLDatabase::DeleteURLRow(URLID id) {
    200   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    201       "DELETE FROM urls WHERE id = ?"));
    202   statement.BindInt64(0, id);
    203 
    204   if (!statement.Run())
    205     return false;
    206 
    207   // And delete any keyword visits.
    208   return !has_keyword_search_terms_ || DeleteKeywordSearchTermForURL(id);
    209 }
    210 
    211 bool URLDatabase::CreateTemporaryURLTable() {
    212   return CreateURLTable(true);
    213 }
    214 
    215 bool URLDatabase::CommitTemporaryURLTable() {
    216   // See the comments in the header file as well as
    217   // HistoryBackend::DeleteAllHistory() for more information on how this works
    218   // and why it does what it does.
    219 
    220   // Swap the url table out and replace it with the temporary one.
    221   if (!GetDB().Execute("DROP TABLE urls")) {
    222     NOTREACHED() << GetDB().GetErrorMessage();
    223     return false;
    224   }
    225   if (!GetDB().Execute("ALTER TABLE temp_urls RENAME TO urls")) {
    226     NOTREACHED() << GetDB().GetErrorMessage();
    227     return false;
    228   }
    229 
    230   // Re-create the index over the now permanent URLs table -- this was not there
    231   // for the temporary table.
    232   CreateMainURLIndex();
    233 
    234   return true;
    235 }
    236 
    237 bool URLDatabase::InitURLEnumeratorForEverything(URLEnumerator* enumerator) {
    238   DCHECK(!enumerator->initialized_);
    239   std::string sql("SELECT ");
    240   sql.append(kURLRowFields);
    241   sql.append(" FROM urls");
    242   enumerator->statement_.Assign(GetDB().GetUniqueStatement(sql.c_str()));
    243   enumerator->initialized_ = enumerator->statement_.is_valid();
    244   return enumerator->statement_.is_valid();
    245 }
    246 
    247 bool URLDatabase::InitURLEnumeratorForSignificant(URLEnumerator* enumerator) {
    248   DCHECK(!enumerator->initialized_);
    249   std::string sql("SELECT ");
    250   sql.append(kURLRowFields);
    251   sql.append(" FROM urls WHERE last_visit_time >= ? OR visit_count >= ? OR "
    252              "typed_count >= ?");
    253   enumerator->statement_.Assign(GetDB().GetUniqueStatement(sql.c_str()));
    254   enumerator->statement_.BindInt64(
    255       0, AutocompleteAgeThreshold().ToInternalValue());
    256   enumerator->statement_.BindInt(1, kLowQualityMatchVisitLimit);
    257   enumerator->statement_.BindInt(2, kLowQualityMatchTypedLimit);
    258   enumerator->initialized_ = enumerator->statement_.is_valid();
    259   return enumerator->statement_.is_valid();
    260 }
    261 
    262 bool URLDatabase::AutocompleteForPrefix(const std::string& prefix,
    263                                         size_t max_results,
    264                                         bool typed_only,
    265                                         URLRows* results) {
    266   // NOTE: this query originally sorted by starred as the second parameter. But
    267   // as bookmarks is no longer part of the db we no longer include the order
    268   // by clause.
    269   results->clear();
    270   const char* sql;
    271   int line;
    272   if (typed_only) {
    273     sql = "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls "
    274         "WHERE url >= ? AND url < ? AND hidden = 0 AND typed_count > 0 "
    275         "ORDER BY typed_count DESC, visit_count DESC, last_visit_time DESC "
    276         "LIMIT ?";
    277     line = __LINE__;
    278   } else {
    279     sql = "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls "
    280         "WHERE url >= ? AND url < ? AND hidden = 0 "
    281         "ORDER BY typed_count DESC, visit_count DESC, last_visit_time DESC "
    282         "LIMIT ?";
    283     line = __LINE__;
    284   }
    285   sql::Statement statement(
    286       GetDB().GetCachedStatement(sql::StatementID(__FILE__, line), sql));
    287 
    288   // We will find all strings between "prefix" and this string, which is prefix
    289   // followed by the maximum character size. Use 8-bit strings for everything
    290   // so we can be sure sqlite is comparing everything in 8-bit mode. Otherwise,
    291   // it will have to convert strings either to UTF-8 or UTF-16 for comparison.
    292   std::string end_query(prefix);
    293   end_query.push_back(std::numeric_limits<unsigned char>::max());
    294 
    295   statement.BindString(0, prefix);
    296   statement.BindString(1, end_query);
    297   statement.BindInt(2, static_cast<int>(max_results));
    298 
    299   while (statement.Step()) {
    300     history::URLRow info;
    301     FillURLRow(statement, &info);
    302     if (info.url().is_valid())
    303       results->push_back(info);
    304   }
    305   return !results->empty();
    306 }
    307 
    308 bool URLDatabase::IsTypedHost(const std::string& host) {
    309   const char* schemes[] = {
    310     url::kHttpScheme,
    311     url::kHttpsScheme,
    312     url::kFtpScheme
    313   };
    314   URLRows dummy;
    315   for (size_t i = 0; i < arraysize(schemes); ++i) {
    316     std::string scheme_and_host(schemes[i]);
    317     scheme_and_host += url::kStandardSchemeSeparator + host;
    318     if (AutocompleteForPrefix(scheme_and_host + '/', 1, true, &dummy) ||
    319         AutocompleteForPrefix(scheme_and_host + ':', 1, true, &dummy))
    320       return true;
    321   }
    322   return false;
    323 }
    324 
    325 bool URLDatabase::FindShortestURLFromBase(const std::string& base,
    326                                           const std::string& url,
    327                                           int min_visits,
    328                                           int min_typed,
    329                                           bool allow_base,
    330                                           history::URLRow* info) {
    331   // Select URLs that start with |base| and are prefixes of |url|.  All parts
    332   // of this query except the substr() call can be done using the index.  We
    333   // could do this query with a couple of LIKE or GLOB statements as well, but
    334   // those wouldn't use the index, and would run into problems with "wildcard"
    335   // characters that appear in URLs (% for LIKE, or *, ? for GLOB).
    336   std::string sql("SELECT ");
    337   sql.append(kURLRowFields);
    338   sql.append(" FROM urls WHERE url ");
    339   sql.append(allow_base ? ">=" : ">");
    340   sql.append(" ? AND url < :end AND url = substr(:end, 1, length(url)) "
    341              "AND hidden = 0 AND visit_count >= ? AND typed_count >= ? "
    342              "ORDER BY url LIMIT 1");
    343   sql::Statement statement(GetDB().GetUniqueStatement(sql.c_str()));
    344   statement.BindString(0, base);
    345   statement.BindString(1, url);   // :end
    346   statement.BindInt(2, min_visits);
    347   statement.BindInt(3, min_typed);
    348 
    349   if (!statement.Step())
    350     return false;
    351 
    352   DCHECK(info);
    353   FillURLRow(statement, info);
    354   return true;
    355 }
    356 
    357 bool URLDatabase::GetTextMatches(const base::string16& query,
    358                                  URLRows* results) {
    359   ScopedVector<query_parser::QueryNode> query_nodes;
    360   query_parser_.ParseQueryNodes(query, &query_nodes.get());
    361 
    362   results->clear();
    363   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    364       "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE hidden = 0"));
    365 
    366   while (statement.Step()) {
    367     query_parser::QueryWordVector query_words;
    368     base::string16 url = base::i18n::ToLower(statement.ColumnString16(1));
    369     query_parser_.ExtractQueryWords(url, &query_words);
    370     GURL gurl(url);
    371     if (gurl.is_valid()) {
    372       // Decode punycode to match IDN.
    373       // |query_words| won't be shown to user - therefore we can use empty
    374       // |languages| to reduce dependency (no need to call PrefService).
    375       base::string16 ascii = base::ASCIIToUTF16(gurl.host());
    376       base::string16 utf = net::IDNToUnicode(gurl.host(), std::string());
    377       if (ascii != utf)
    378         query_parser_.ExtractQueryWords(utf, &query_words);
    379     }
    380     base::string16 title = base::i18n::ToLower(statement.ColumnString16(2));
    381     query_parser_.ExtractQueryWords(title, &query_words);
    382 
    383     if (query_parser_.DoesQueryMatch(query_words, query_nodes.get())) {
    384       history::URLResult info;
    385       FillURLRow(statement, &info);
    386       if (info.url().is_valid())
    387         results->push_back(info);
    388     }
    389   }
    390   return !results->empty();
    391 }
    392 
    393 bool URLDatabase::InitKeywordSearchTermsTable() {
    394   has_keyword_search_terms_ = true;
    395   if (!GetDB().DoesTableExist("keyword_search_terms")) {
    396     if (!GetDB().Execute("CREATE TABLE keyword_search_terms ("
    397         "keyword_id INTEGER NOT NULL,"      // ID of the TemplateURL.
    398         "url_id INTEGER NOT NULL,"          // ID of the url.
    399         "lower_term LONGVARCHAR NOT NULL,"  // The search term, in lower case.
    400         "term LONGVARCHAR NOT NULL)"))      // The actual search term.
    401       return false;
    402   }
    403   return true;
    404 }
    405 
    406 bool URLDatabase::CreateKeywordSearchTermsIndices() {
    407   // For searching.
    408   if (!GetDB().Execute(
    409           "CREATE INDEX IF NOT EXISTS keyword_search_terms_index1 ON "
    410           "keyword_search_terms (keyword_id, lower_term)")) {
    411     return false;
    412   }
    413 
    414   // For deletion.
    415   if (!GetDB().Execute(
    416           "CREATE INDEX IF NOT EXISTS keyword_search_terms_index2 ON "
    417           "keyword_search_terms (url_id)")) {
    418     return false;
    419   }
    420 
    421   // For query or deletion by term.
    422   if (!GetDB().Execute(
    423           "CREATE INDEX IF NOT EXISTS keyword_search_terms_index3 ON "
    424           "keyword_search_terms (term)")) {
    425     return false;
    426   }
    427   return true;
    428 }
    429 
    430 bool URLDatabase::DropKeywordSearchTermsTable() {
    431   // This will implicitly delete the indices over the table.
    432   return GetDB().Execute("DROP TABLE keyword_search_terms");
    433 }
    434 
    435 bool URLDatabase::SetKeywordSearchTermsForURL(URLID url_id,
    436                                               KeywordID keyword_id,
    437                                               const base::string16& term) {
    438   DCHECK(url_id && keyword_id && !term.empty());
    439 
    440   sql::Statement exist_statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    441       "SELECT term FROM keyword_search_terms "
    442       "WHERE keyword_id = ? AND url_id = ?"));
    443   exist_statement.BindInt64(0, keyword_id);
    444   exist_statement.BindInt64(1, url_id);
    445 
    446   if (exist_statement.Step())
    447     return true;  // Term already exists, no need to add it.
    448 
    449   if (!exist_statement.Succeeded())
    450     return false;
    451 
    452   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    453       "INSERT INTO keyword_search_terms (keyword_id, url_id, lower_term, term) "
    454       "VALUES (?,?,?,?)"));
    455   statement.BindInt64(0, keyword_id);
    456   statement.BindInt64(1, url_id);
    457   statement.BindString16(2, base::i18n::ToLower(term));
    458   statement.BindString16(3, term);
    459   return statement.Run();
    460 }
    461 
    462 bool URLDatabase::GetKeywordSearchTermRow(URLID url_id,
    463                                           KeywordSearchTermRow* row) {
    464   DCHECK(url_id);
    465   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    466       "SELECT keyword_id, term FROM keyword_search_terms WHERE url_id=?"));
    467   statement.BindInt64(0, url_id);
    468 
    469   if (!statement.Step())
    470     return false;
    471 
    472   if (row) {
    473     row->url_id = url_id;
    474     row->keyword_id = statement.ColumnInt64(0);
    475     row->term = statement.ColumnString16(1);
    476   }
    477   return true;
    478 }
    479 
    480 bool URLDatabase::GetKeywordSearchTermRows(
    481     const base::string16& term,
    482     std::vector<KeywordSearchTermRow>* rows) {
    483   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    484       "SELECT keyword_id, url_id FROM keyword_search_terms WHERE term=?"));
    485   statement.BindString16(0, term);
    486 
    487   if (!statement.is_valid())
    488     return false;
    489 
    490   while (statement.Step()) {
    491     KeywordSearchTermRow row;
    492     row.url_id = statement.ColumnInt64(1);
    493     row.keyword_id = statement.ColumnInt64(0);
    494     row.term = term;
    495     rows->push_back(row);
    496   }
    497   return true;
    498 }
    499 
    500 void URLDatabase::DeleteAllSearchTermsForKeyword(
    501     KeywordID keyword_id) {
    502   DCHECK(keyword_id);
    503   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    504       "DELETE FROM keyword_search_terms WHERE keyword_id=?"));
    505   statement.BindInt64(0, keyword_id);
    506 
    507   statement.Run();
    508 }
    509 
    510 void URLDatabase::GetMostRecentKeywordSearchTerms(
    511     KeywordID keyword_id,
    512     const base::string16& prefix,
    513     int max_count,
    514     std::vector<KeywordSearchTermVisit>* matches) {
    515   // NOTE: the keyword_id can be zero if on first run the user does a query
    516   // before the TemplateURLService has finished loading. As the chances of this
    517   // occurring are small, we ignore it.
    518   if (!keyword_id)
    519     return;
    520 
    521   DCHECK(!prefix.empty());
    522   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    523       "SELECT DISTINCT kv.term, u.visit_count, u.last_visit_time "
    524       "FROM keyword_search_terms kv "
    525       "JOIN urls u ON kv.url_id = u.id "
    526       "WHERE kv.keyword_id = ? AND kv.lower_term >= ? AND kv.lower_term < ? "
    527       "ORDER BY u.last_visit_time DESC LIMIT ?"));
    528 
    529   // NOTE: Keep this ToLower() call in sync with search_provider.cc.
    530   base::string16 lower_prefix = base::i18n::ToLower(prefix);
    531   // This magic gives us a prefix search.
    532   base::string16 next_prefix = lower_prefix;
    533   next_prefix[next_prefix.size() - 1] =
    534       next_prefix[next_prefix.size() - 1] + 1;
    535   statement.BindInt64(0, keyword_id);
    536   statement.BindString16(1, lower_prefix);
    537   statement.BindString16(2, next_prefix);
    538   statement.BindInt(3, max_count);
    539 
    540   KeywordSearchTermVisit visit;
    541   while (statement.Step()) {
    542     visit.term = statement.ColumnString16(0);
    543     visit.visits = statement.ColumnInt(1);
    544     visit.time = base::Time::FromInternalValue(statement.ColumnInt64(2));
    545     matches->push_back(visit);
    546   }
    547 }
    548 
    549 bool URLDatabase::DeleteKeywordSearchTerm(const base::string16& term) {
    550   sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
    551       "DELETE FROM keyword_search_terms WHERE term=?"));
    552   statement.BindString16(0, term);
    553 
    554   return statement.Run();
    555 }
    556 
    557 bool URLDatabase::DeleteKeywordSearchTermForURL(URLID url_id) {
    558   sql::Statement statement(GetDB().GetCachedStatement(
    559       SQL_FROM_HERE, "DELETE FROM keyword_search_terms WHERE url_id=?"));
    560   statement.BindInt64(0, url_id);
    561   return statement.Run();
    562 }
    563 
    564 bool URLDatabase::DropStarredIDFromURLs() {
    565   if (!GetDB().DoesColumnExist("urls", "starred_id"))
    566     return true;  // urls is already updated, no need to continue.
    567 
    568   // Create a temporary table to contain the new URLs table.
    569   if (!CreateTemporaryURLTable()) {
    570     NOTREACHED();
    571     return false;
    572   }
    573 
    574   // Copy the contents.
    575   if (!GetDB().Execute(
    576       "INSERT INTO temp_urls (id, url, title, visit_count, typed_count, "
    577       "last_visit_time, hidden, favicon_id) "
    578       "SELECT id, url, title, visit_count, typed_count, last_visit_time, "
    579       "hidden, favicon_id FROM urls")) {
    580     NOTREACHED() << GetDB().GetErrorMessage();
    581     return false;
    582   }
    583 
    584   // Rename/commit the tmp table.
    585   CommitTemporaryURLTable();
    586 
    587   return true;
    588 }
    589 
    590 bool URLDatabase::CreateURLTable(bool is_temporary) {
    591   const char* name = is_temporary ? "temp_urls" : "urls";
    592   if (GetDB().DoesTableExist(name))
    593     return true;
    594 
    595   // Note: revise implementation for InsertOrUpdateURLRowByID() if you add any
    596   // new constraints to the schema.
    597   std::string sql;
    598   sql.append("CREATE TABLE ");
    599   sql.append(name);
    600   sql.append("("
    601       "id INTEGER PRIMARY KEY,"
    602       "url LONGVARCHAR,"
    603       "title LONGVARCHAR,"
    604       "visit_count INTEGER DEFAULT 0 NOT NULL,"
    605       "typed_count INTEGER DEFAULT 0 NOT NULL,"
    606       "last_visit_time INTEGER NOT NULL,"
    607       "hidden INTEGER DEFAULT 0 NOT NULL,"
    608       "favicon_id INTEGER DEFAULT 0 NOT NULL)"); // favicon_id is not used now.
    609 
    610   return GetDB().Execute(sql.c_str());
    611 }
    612 
    613 bool URLDatabase::CreateMainURLIndex() {
    614   return GetDB().Execute(
    615       "CREATE INDEX IF NOT EXISTS urls_url_index ON urls (url)");
    616 }
    617 
    618 const int kLowQualityMatchTypedLimit = 1;
    619 const int kLowQualityMatchVisitLimit = 4;
    620 const int kLowQualityMatchAgeLimitInDays = 3;
    621 
    622 base::Time AutocompleteAgeThreshold() {
    623   return (base::Time::Now() -
    624           base::TimeDelta::FromDays(kLowQualityMatchAgeLimitInDays));
    625 }
    626 
    627 bool RowQualifiesAsSignificant(const URLRow& row,
    628                                const base::Time& threshold) {
    629   const base::Time& real_threshold =
    630       threshold.is_null() ? AutocompleteAgeThreshold() : threshold;
    631   return (row.typed_count() >= kLowQualityMatchTypedLimit) ||
    632          (row.visit_count() >= kLowQualityMatchVisitLimit) ||
    633          (row.last_visit() >= real_threshold);
    634 }
    635 
    636 }  // namespace history
    637