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