| OLD | NEW |
| (Empty) |
| 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 | |
| 25 URLDatabase::URLEnumeratorBase::URLEnumeratorBase() | |
| 26 : initialized_(false) { | |
| 27 } | |
| 28 | |
| 29 URLDatabase::URLEnumeratorBase::~URLEnumeratorBase() { | |
| 30 } | |
| 31 | |
| 32 URLDatabase::URLEnumerator::URLEnumerator() { | |
| 33 } | |
| 34 | |
| 35 bool URLDatabase::URLEnumerator::GetNextURL(URLRow* r) { | |
| 36 if (statement_.Step()) { | |
| 37 FillURLRow(statement_, r); | |
| 38 return true; | |
| 39 } | |
| 40 return false; | |
| 41 } | |
| 42 | |
| 43 URLDatabase::URLDatabase() | |
| 44 : has_keyword_search_terms_(false) { | |
| 45 } | |
| 46 | |
| 47 URLDatabase::~URLDatabase() { | |
| 48 } | |
| 49 | |
| 50 // static | |
| 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. | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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() && GetDB().GetLastChangeCount() > 0; | |
| 131 } | |
| 132 | |
| 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 | |
| 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 | |
| 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 | |
| 212 bool URLDatabase::CreateTemporaryURLTable() { | |
| 213 return CreateURLTable(true); | |
| 214 } | |
| 215 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 436 bool URLDatabase::SetKeywordSearchTermsForURL(URLID url_id, | |
| 437 KeywordID 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 | |
| 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 | |
| 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 | |
| 501 void URLDatabase::DeleteAllSearchTermsForKeyword( | |
| 502 KeywordID 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 | |
| 511 void URLDatabase::GetMostRecentKeywordSearchTerms( | |
| 512 KeywordID 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| 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 | |
| OLD | NEW |