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