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