1 // Copyright (c) 2010 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 "app/sql/statement.h" 13 #include "base/utf_string_conversions.h" 14 #include "chrome/common/url_constants.h" 15 #include "googleurl/src/gurl.h" 16 #include "ui/base/l10n/l10n_util.h" 17 18 namespace history { 19 20 const char URLDatabase::kURLRowFields[] = HISTORY_URL_ROW_FIELDS; 21 const int URLDatabase::kNumURLRowFields = 9; 22 23 URLDatabase::URLEnumeratorBase::URLEnumeratorBase() 24 : initialized_(false) { 25 } 26 27 URLDatabase::URLEnumeratorBase::~URLEnumeratorBase() { 28 } 29 30 URLDatabase::URLEnumerator::URLEnumerator() { 31 } 32 33 URLDatabase::IconMappingEnumerator::IconMappingEnumerator() { 34 } 35 36 bool URLDatabase::URLEnumerator::GetNextURL(URLRow* r) { 37 if (statement_.Step()) { 38 FillURLRow(statement_, r); 39 return true; 40 } 41 return false; 42 } 43 44 bool URLDatabase::IconMappingEnumerator::GetNextIconMapping(IconMapping* r) { 45 if (!statement_.Step()) 46 return false; 47 48 r->page_url = GURL(statement_.ColumnString(0)); 49 r->icon_id = statement_.ColumnInt64(1); 50 return true; 51 } 52 53 URLDatabase::URLDatabase() 54 : has_keyword_search_terms_(false) { 55 } 56 57 URLDatabase::~URLDatabase() { 58 } 59 60 // static 61 std::string URLDatabase::GURLToDatabaseURL(const GURL& gurl) { 62 // TODO(brettw): do something fancy here with encoding, etc. 63 64 // Strip username and password from URL before sending to DB. 65 GURL::Replacements replacements; 66 replacements.ClearUsername(); 67 replacements.ClearPassword(); 68 69 return (gurl.ReplaceComponents(replacements)).spec(); 70 } 71 72 // Convenience to fill a history::URLRow. Must be in sync with the fields in 73 // kURLRowFields. 74 void URLDatabase::FillURLRow(sql::Statement& s, history::URLRow* i) { 75 DCHECK(i); 76 i->id_ = s.ColumnInt64(0); 77 i->url_ = GURL(s.ColumnString(1)); 78 i->title_ = s.ColumnString16(2); 79 i->visit_count_ = s.ColumnInt(3); 80 i->typed_count_ = s.ColumnInt(4); 81 i->last_visit_ = base::Time::FromInternalValue(s.ColumnInt64(5)); 82 i->hidden_ = s.ColumnInt(6) != 0; 83 } 84 85 bool URLDatabase::GetURLRow(URLID url_id, URLRow* info) { 86 // TODO(brettw) We need check for empty URLs to handle the case where 87 // there are old URLs in the database that are empty that got in before 88 // we added any checks. We should eventually be able to remove it 89 // when all inputs are using GURL (which prohibit empty input). 90 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 91 "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE id=?")); 92 if (!statement) 93 return false; 94 95 statement.BindInt64(0, url_id); 96 if (statement.Step()) { 97 FillURLRow(statement, info); 98 return true; 99 } 100 return false; 101 } 102 103 bool URLDatabase::GetAllTypedUrls(std::vector<history::URLRow>* urls) { 104 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 105 "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE typed_count > 0")); 106 if (!statement) 107 return false; 108 109 while (statement.Step()) { 110 URLRow info; 111 FillURLRow(statement, &info); 112 urls->push_back(info); 113 } 114 return true; 115 } 116 117 URLID URLDatabase::GetRowForURL(const GURL& url, history::URLRow* info) { 118 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 119 "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE url=?")); 120 if (!statement) 121 return 0; 122 123 std::string url_string = GURLToDatabaseURL(url); 124 statement.BindString(0, url_string); 125 if (!statement.Step()) 126 return 0; // no data 127 128 if (info) 129 FillURLRow(statement, info); 130 return statement.ColumnInt64(0); 131 } 132 133 bool URLDatabase::UpdateURLRow(URLID url_id, 134 const history::URLRow& info) { 135 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 136 "UPDATE urls SET title=?,visit_count=?,typed_count=?,last_visit_time=?," 137 "hidden=?" 138 "WHERE id=?")); 139 if (!statement) 140 return false; 141 142 statement.BindString16(0, info.title()); 143 statement.BindInt(1, info.visit_count()); 144 statement.BindInt(2, info.typed_count()); 145 statement.BindInt64(3, info.last_visit().ToInternalValue()); 146 statement.BindInt(4, info.hidden() ? 1 : 0); 147 statement.BindInt64(5, url_id); 148 return statement.Run(); 149 } 150 151 URLID URLDatabase::AddURLInternal(const history::URLRow& info, 152 bool is_temporary) { 153 // This function is used to insert into two different tables, so we have to 154 // do some shuffling. Unfortinately, we can't use the macro 155 // HISTORY_URL_ROW_FIELDS because that specifies the table name which is 156 // invalid in the insert syntax. 157 #define ADDURL_COMMON_SUFFIX \ 158 " (url, title, visit_count, typed_count, "\ 159 "last_visit_time, hidden) "\ 160 "VALUES (?,?,?,?,?,?)" 161 const char* statement_name; 162 const char* statement_sql; 163 if (is_temporary) { 164 statement_name = "AddURLTemporary"; 165 statement_sql = "INSERT INTO temp_urls" ADDURL_COMMON_SUFFIX; 166 } else { 167 statement_name = "AddURL"; 168 statement_sql = "INSERT INTO urls" ADDURL_COMMON_SUFFIX; 169 } 170 #undef ADDURL_COMMON_SUFFIX 171 172 sql::Statement statement(GetDB().GetCachedStatement( 173 sql::StatementID(statement_name), statement_sql)); 174 if (!statement) { 175 NOTREACHED() << GetDB().GetErrorMessage(); 176 return 0; 177 } 178 179 statement.BindString(0, GURLToDatabaseURL(info.url())); 180 statement.BindString16(1, info.title()); 181 statement.BindInt(2, info.visit_count()); 182 statement.BindInt(3, info.typed_count()); 183 statement.BindInt64(4, info.last_visit().ToInternalValue()); 184 statement.BindInt(5, info.hidden() ? 1 : 0); 185 186 if (!statement.Run()) { 187 VLOG(0) << "Failed to add url " << info.url().possibly_invalid_spec() 188 << " to table history.urls."; 189 return 0; 190 } 191 return GetDB().GetLastInsertRowId(); 192 } 193 194 bool URLDatabase::DeleteURLRow(URLID id) { 195 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 196 "DELETE FROM urls WHERE id = ?")); 197 if (!statement) 198 return false; 199 200 statement.BindInt64(0, id); 201 if (!statement.Run()) 202 return false; 203 204 // And delete any keyword visits. 205 if (!has_keyword_search_terms_) 206 return true; 207 208 sql::Statement del_keyword_visit(GetDB().GetCachedStatement(SQL_FROM_HERE, 209 "DELETE FROM keyword_search_terms WHERE url_id=?")); 210 if (!del_keyword_visit) 211 return false; 212 del_keyword_visit.BindInt64(0, id); 213 return del_keyword_visit.Run(); 214 } 215 216 bool URLDatabase::CreateTemporaryURLTable() { 217 return CreateURLTable(true); 218 } 219 220 bool URLDatabase::CommitTemporaryURLTable() { 221 // See the comments in the header file as well as 222 // HistoryBackend::DeleteAllHistory() for more information on how this works 223 // and why it does what it does. 224 // 225 // Note that the main database overrides this to additionally create the 226 // supplimentary indices that the archived database doesn't need. 227 228 // Swap the url table out and replace it with the temporary one. 229 if (!GetDB().Execute("DROP TABLE urls")) { 230 NOTREACHED() << GetDB().GetErrorMessage(); 231 return false; 232 } 233 if (!GetDB().Execute("ALTER TABLE temp_urls RENAME TO urls")) { 234 NOTREACHED() << GetDB().GetErrorMessage(); 235 return false; 236 } 237 238 // Create the index over URLs. This is needed for the main, in-memory, and 239 // archived databases, so we always do it. The supplimentary indices used by 240 // the main database are not created here. When deleting all history, they 241 // are created by HistoryDatabase::RecreateAllButStarAndURLTables(). 242 CreateMainURLIndex(); 243 244 return true; 245 } 246 247 bool URLDatabase::InitURLEnumeratorForEverything(URLEnumerator* enumerator) { 248 DCHECK(!enumerator->initialized_); 249 std::string sql("SELECT "); 250 sql.append(kURLRowFields); 251 sql.append(" FROM urls"); 252 enumerator->statement_.Assign(GetDB().GetUniqueStatement(sql.c_str())); 253 if (!enumerator->statement_) { 254 NOTREACHED() << GetDB().GetErrorMessage(); 255 return false; 256 } 257 enumerator->initialized_ = true; 258 return true; 259 } 260 261 bool URLDatabase::InitURLEnumeratorForSignificant(URLEnumerator* enumerator) { 262 DCHECK(!enumerator->initialized_); 263 std::string sql("SELECT "); 264 sql.append(kURLRowFields); 265 sql.append(" FROM urls WHERE last_visit_time >= ? OR visit_count > ? OR " 266 "typed_count > ?"); 267 enumerator->statement_.Assign(GetDB().GetUniqueStatement(sql.c_str())); 268 if (!enumerator->statement_) { 269 NOTREACHED() << GetDB().GetErrorMessage(); 270 return false; 271 } 272 enumerator->statement_.BindInt64( 273 0, AutocompleteAgeThreshold().ToInternalValue()); 274 enumerator->statement_.BindInt(1, kLowQualityMatchVisitLimit); 275 enumerator->statement_.BindInt(2, kLowQualityMatchTypedLimit); 276 enumerator->initialized_ = true; 277 return true; 278 } 279 280 bool URLDatabase::InitIconMappingEnumeratorForEverything( 281 IconMappingEnumerator* enumerator) { 282 DCHECK(!enumerator->initialized_); 283 enumerator->statement_.Assign(GetDB().GetUniqueStatement( 284 "SELECT url, favicon_id FROM urls WHERE favicon_id <> 0")); 285 if (!enumerator->statement_) { 286 NOTREACHED() << GetDB().GetErrorMessage(); 287 return false; 288 } 289 enumerator->initialized_ = true; 290 return true; 291 } 292 293 void URLDatabase::AutocompleteForPrefix(const string16& prefix, 294 size_t max_results, 295 bool typed_only, 296 std::vector<history::URLRow>* results) { 297 // NOTE: this query originally sorted by starred as the second parameter. But 298 // as bookmarks is no longer part of the db we no longer include the order 299 // by clause. 300 results->clear(); 301 const char* sql; 302 int line; 303 if (typed_only) { 304 sql = "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls " 305 "WHERE url >= ? AND url < ? AND hidden = 0 AND typed_count > 0 " 306 "ORDER BY typed_count DESC, visit_count DESC, last_visit_time DESC " 307 "LIMIT ?"; 308 line = __LINE__; 309 } else { 310 sql = "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls " 311 "WHERE url >= ? AND url < ? AND hidden = 0 " 312 "ORDER BY typed_count DESC, visit_count DESC, last_visit_time DESC " 313 "LIMIT ?"; 314 line = __LINE__; 315 } 316 sql::Statement statement( 317 GetDB().GetCachedStatement(sql::StatementID(__FILE__, line), sql)); 318 if (!statement) 319 return; 320 321 // We will find all strings between "prefix" and this string, which is prefix 322 // followed by the maximum character size. Use 8-bit strings for everything 323 // so we can be sure sqlite is comparing everything in 8-bit mode. Otherwise, 324 // it will have to convert strings either to UTF-8 or UTF-16 for comparison. 325 std::string prefix_utf8(UTF16ToUTF8(prefix)); 326 std::string end_query(prefix_utf8); 327 end_query.push_back(std::numeric_limits<unsigned char>::max()); 328 329 statement.BindString(0, prefix_utf8); 330 statement.BindString(1, end_query); 331 statement.BindInt(2, static_cast<int>(max_results)); 332 333 while (statement.Step()) { 334 history::URLRow info; 335 FillURLRow(statement, &info); 336 if (info.url().is_valid()) 337 results->push_back(info); 338 } 339 } 340 341 bool URLDatabase::FindShortestURLFromBase(const std::string& base, 342 const std::string& url, 343 int min_visits, 344 int min_typed, 345 bool allow_base, 346 history::URLRow* info) { 347 // Select URLs that start with |base| and are prefixes of |url|. All parts 348 // of this query except the substr() call can be done using the index. We 349 // could do this query with a couple of LIKE or GLOB statements as well, but 350 // those wouldn't use the index, and would run into problems with "wildcard" 351 // characters that appear in URLs (% for LIKE, or *, ? for GLOB). 352 std::string sql("SELECT "); 353 sql.append(kURLRowFields); 354 sql.append(" FROM urls WHERE url "); 355 sql.append(allow_base ? ">=" : ">"); 356 sql.append(" ? AND url < :end AND url = substr(:end, 1, length(url)) " 357 "AND hidden = 0 AND visit_count >= ? AND typed_count >= ? " 358 "ORDER BY url LIMIT 1"); 359 sql::Statement statement(GetDB().GetUniqueStatement(sql.c_str())); 360 if (!statement) { 361 NOTREACHED() << GetDB().GetErrorMessage(); 362 return false; 363 } 364 365 statement.BindString(0, base); 366 statement.BindString(1, url); // :end 367 statement.BindInt(2, min_visits); 368 statement.BindInt(3, min_typed); 369 370 if (!statement.Step()) 371 return false; 372 373 DCHECK(info); 374 FillURLRow(statement, info); 375 return true; 376 } 377 378 bool URLDatabase::InitKeywordSearchTermsTable() { 379 has_keyword_search_terms_ = true; 380 if (!GetDB().DoesTableExist("keyword_search_terms")) { 381 if (!GetDB().Execute("CREATE TABLE keyword_search_terms (" 382 "keyword_id INTEGER NOT NULL," // ID of the TemplateURL. 383 "url_id INTEGER NOT NULL," // ID of the url. 384 "lower_term LONGVARCHAR NOT NULL," // The search term, in lower case. 385 "term LONGVARCHAR NOT NULL)")) // The actual search term. 386 return false; 387 } 388 return true; 389 } 390 391 void URLDatabase::CreateKeywordSearchTermsIndices() { 392 // For searching. 393 GetDB().Execute("CREATE INDEX keyword_search_terms_index1 ON " 394 "keyword_search_terms (keyword_id, lower_term)"); 395 396 // For deletion. 397 GetDB().Execute("CREATE INDEX keyword_search_terms_index2 ON " 398 "keyword_search_terms (url_id)"); 399 } 400 401 bool URLDatabase::DropKeywordSearchTermsTable() { 402 // This will implicitly delete the indices over the table. 403 return GetDB().Execute("DROP TABLE keyword_search_terms"); 404 } 405 406 bool URLDatabase::SetKeywordSearchTermsForURL(URLID url_id, 407 TemplateURLID keyword_id, 408 const string16& term) { 409 DCHECK(url_id && keyword_id && !term.empty()); 410 411 sql::Statement exist_statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 412 "SELECT term FROM keyword_search_terms " 413 "WHERE keyword_id = ? AND url_id = ?")); 414 if (!exist_statement) 415 return false; 416 exist_statement.BindInt64(0, keyword_id); 417 exist_statement.BindInt64(1, url_id); 418 if (exist_statement.Step()) 419 return true; // Term already exists, no need to add it. 420 421 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 422 "INSERT INTO keyword_search_terms (keyword_id, url_id, lower_term, term) " 423 "VALUES (?,?,?,?)")); 424 if (!statement) 425 return false; 426 427 statement.BindInt64(0, keyword_id); 428 statement.BindInt64(1, url_id); 429 statement.BindString16(2, l10n_util::ToLower(term)); 430 statement.BindString16(3, term); 431 return statement.Run(); 432 } 433 434 bool URLDatabase::GetKeywordSearchTermRow(URLID url_id, 435 KeywordSearchTermRow* row) { 436 DCHECK(url_id); 437 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 438 "SELECT keyword_id, term FROM keyword_search_terms WHERE url_id=?")); 439 if (!statement) 440 return false; 441 442 statement.BindInt64(0, url_id); 443 if (!statement.Step()) 444 return false; 445 446 if (row) { 447 row->url_id = url_id; 448 row->keyword_id = statement.ColumnInt64(0); 449 row->term = statement.ColumnString16(1); 450 } 451 return true; 452 } 453 454 void URLDatabase::DeleteAllSearchTermsForKeyword( 455 TemplateURLID keyword_id) { 456 DCHECK(keyword_id); 457 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 458 "DELETE FROM keyword_search_terms WHERE keyword_id=?")); 459 if (!statement) 460 return; 461 462 statement.BindInt64(0, keyword_id); 463 statement.Run(); 464 } 465 466 void URLDatabase::GetMostRecentKeywordSearchTerms( 467 TemplateURLID keyword_id, 468 const string16& prefix, 469 int max_count, 470 std::vector<KeywordSearchTermVisit>* matches) { 471 // NOTE: the keyword_id can be zero if on first run the user does a query 472 // before the TemplateURLModel has finished loading. As the chances of this 473 // occurring are small, we ignore it. 474 if (!keyword_id) 475 return; 476 477 DCHECK(!prefix.empty()); 478 sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE, 479 "SELECT DISTINCT kv.term, u.last_visit_time " 480 "FROM keyword_search_terms kv " 481 "JOIN urls u ON kv.url_id = u.id " 482 "WHERE kv.keyword_id = ? AND kv.lower_term >= ? AND kv.lower_term < ? " 483 "ORDER BY u.last_visit_time DESC LIMIT ?")); 484 if (!statement) 485 return; 486 487 // NOTE: Keep this ToLower() call in sync with search_provider.cc. 488 string16 lower_prefix = l10n_util::ToLower(prefix); 489 // This magic gives us a prefix search. 490 string16 next_prefix = lower_prefix; 491 next_prefix[next_prefix.size() - 1] = 492 next_prefix[next_prefix.size() - 1] + 1; 493 statement.BindInt64(0, keyword_id); 494 statement.BindString16(1, lower_prefix); 495 statement.BindString16(2, next_prefix); 496 statement.BindInt(3, max_count); 497 498 KeywordSearchTermVisit visit; 499 while (statement.Step()) { 500 visit.term = statement.ColumnString16(0); 501 visit.time = base::Time::FromInternalValue(statement.ColumnInt64(1)); 502 matches->push_back(visit); 503 } 504 } 505 506 bool URLDatabase::DropStarredIDFromURLs() { 507 if (!GetDB().DoesColumnExist("urls", "starred_id")) 508 return true; // urls is already updated, no need to continue. 509 510 // Create a temporary table to contain the new URLs table. 511 if (!CreateTemporaryURLTable()) { 512 NOTREACHED(); 513 return false; 514 } 515 516 // Copy the contents. 517 if (!GetDB().Execute( 518 "INSERT INTO temp_urls (id, url, title, visit_count, typed_count, " 519 "last_visit_time, hidden, favicon_id) " 520 "SELECT id, url, title, visit_count, typed_count, last_visit_time, " 521 "hidden, favicon_id FROM urls")) { 522 NOTREACHED() << GetDB().GetErrorMessage(); 523 return false; 524 } 525 526 // Rename/commit the tmp table. 527 CommitTemporaryURLTable(); 528 529 return true; 530 } 531 532 bool URLDatabase::CreateURLTable(bool is_temporary) { 533 const char* name = is_temporary ? "temp_urls" : "urls"; 534 if (GetDB().DoesTableExist(name)) 535 return true; 536 537 std::string sql; 538 sql.append("CREATE TABLE "); 539 sql.append(name); 540 sql.append("(" 541 "id INTEGER PRIMARY KEY," 542 "url LONGVARCHAR," 543 "title LONGVARCHAR," 544 "visit_count INTEGER DEFAULT 0 NOT NULL," 545 "typed_count INTEGER DEFAULT 0 NOT NULL," 546 "last_visit_time INTEGER NOT NULL," 547 "hidden INTEGER DEFAULT 0 NOT NULL," 548 "favicon_id INTEGER DEFAULT 0 NOT NULL)"); // favicon_id is not used now. 549 550 return GetDB().Execute(sql.c_str()); 551 } 552 553 void URLDatabase::CreateMainURLIndex() { 554 // Index over URLs so we can quickly look up based on URL. Ignore errors as 555 // this likely already exists (and the same below). 556 GetDB().Execute("CREATE INDEX urls_url_index ON urls (url)"); 557 } 558 559 } // namespace history 560