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 637