• 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/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 
URLEnumeratorBase()25 URLDatabase::URLEnumeratorBase::URLEnumeratorBase()
26     : initialized_(false) {
27 }
28 
~URLEnumeratorBase()29 URLDatabase::URLEnumeratorBase::~URLEnumeratorBase() {
30 }
31 
URLEnumerator()32 URLDatabase::URLEnumerator::URLEnumerator() {
33 }
34 
GetNextURL(URLRow * r)35 bool URLDatabase::URLEnumerator::GetNextURL(URLRow* r) {
36   if (statement_.Step()) {
37     FillURLRow(statement_, r);
38     return true;
39   }
40   return false;
41 }
42 
URLDatabase()43 URLDatabase::URLDatabase()
44     : has_keyword_search_terms_(false) {
45 }
46 
~URLDatabase()47 URLDatabase::~URLDatabase() {
48 }
49 
50 // static
GURLToDatabaseURL(const GURL & gurl)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.
FillURLRow(sql::Statement & s,history::URLRow * i)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 
GetURLRow(URLID url_id,URLRow * info)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 
GetAllTypedUrls(URLRows * urls)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 
GetRowForURL(const GURL & url,history::URLRow * info)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 
UpdateURLRow(URLID url_id,const history::URLRow & info)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 
AddURLInternal(const history::URLRow & info,bool is_temporary)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 
InsertOrUpdateURLRowByID(const history::URLRow & info)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 
DeleteURLRow(URLID id)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 
CreateTemporaryURLTable()212 bool URLDatabase::CreateTemporaryURLTable() {
213   return CreateURLTable(true);
214 }
215 
CommitTemporaryURLTable()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 
InitURLEnumeratorForEverything(URLEnumerator * enumerator)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 
InitURLEnumeratorForSignificant(URLEnumerator * enumerator)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 
AutocompleteForPrefix(const std::string & prefix,size_t max_results,bool typed_only,URLRows * results)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 
IsTypedHost(const std::string & host)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 
FindShortestURLFromBase(const std::string & base,const std::string & url,int min_visits,int min_typed,bool allow_base,history::URLRow * info)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 
GetTextMatches(const base::string16 & query,URLRows * results)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 
InitKeywordSearchTermsTable()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 
CreateKeywordSearchTermsIndices()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 
DropKeywordSearchTermsTable()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 
SetKeywordSearchTermsForURL(URLID url_id,TemplateURLID keyword_id,const base::string16 & term)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 
GetKeywordSearchTermRow(URLID url_id,KeywordSearchTermRow * row)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 
GetKeywordSearchTermRows(const base::string16 & term,std::vector<KeywordSearchTermRow> * rows)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 
DeleteAllSearchTermsForKeyword(TemplateURLID keyword_id)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 
GetMostRecentKeywordSearchTerms(TemplateURLID keyword_id,const base::string16 & prefix,int max_count,std::vector<KeywordSearchTermVisit> * matches)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 
DeleteKeywordSearchTerm(const base::string16 & term)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 
DeleteKeywordSearchTermForURL(URLID url_id)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 
DropStarredIDFromURLs()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 
CreateURLTable(bool is_temporary)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 
CreateMainURLIndex()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