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