summaryrefslogtreecommitdiffstats
path: root/chrome/browser/history/url_database.cc
diff options
context:
space:
mode:
authorkaiwang@chromium.org <kaiwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-19 02:21:43 +0000
committerkaiwang@chromium.org <kaiwang@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-09-19 02:21:43 +0000
commit1414a00dd053ed41304f2d71c26f9edce925131b (patch)
treede23544b2f274a2eba97e40484c6bb8da2b30b22 /chrome/browser/history/url_database.cc
parent63a338022ddd57e68a55f26c6d1d534970e6c9ad (diff)
downloadchromium_src-1414a00dd053ed41304f2d71c26f9edce925131b.zip
chromium_src-1414a00dd053ed41304f2d71c26f9edce925131b.tar.gz
chromium_src-1414a00dd053ed41304f2d71c26f9edce925131b.tar.bz2
Revert 157475 - Introduce chrome/browser/common directory and move url_database(and its dependencies).
c/b/common dir is for utility/library code shared by chrome browser code. BUG=144783 Review URL: https://chromiumcodereview.appspot.com/10908155 TBR=kaiwang@chromium.org Review URL: https://codereview.chromium.org/10941028 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@157477 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/history/url_database.cc')
-rw-r--r--chrome/browser/history/url_database.cc578
1 files changed, 578 insertions, 0 deletions
diff --git a/chrome/browser/history/url_database.cc b/chrome/browser/history/url_database.cc
new file mode 100644
index 0000000..5387bc1
--- /dev/null
+++ b/chrome/browser/history/url_database.cc
@@ -0,0 +1,578 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/history/url_database.h"
+
+#include <algorithm>
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "base/i18n/case_conversion.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/common/url_constants.h"
+#include "googleurl/src/gurl.h"
+#include "sql/statement.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace history {
+
+const char URLDatabase::kURLRowFields[] = HISTORY_URL_ROW_FIELDS;
+const int URLDatabase::kNumURLRowFields = 9;
+
+URLDatabase::URLEnumeratorBase::URLEnumeratorBase()
+ : initialized_(false) {
+}
+
+URLDatabase::URLEnumeratorBase::~URLEnumeratorBase() {
+}
+
+URLDatabase::URLEnumerator::URLEnumerator() {
+}
+
+URLDatabase::IconMappingEnumerator::IconMappingEnumerator() {
+}
+
+bool URLDatabase::URLEnumerator::GetNextURL(URLRow* r) {
+ if (statement_.Step()) {
+ FillURLRow(statement_, r);
+ return true;
+ }
+ return false;
+}
+
+bool URLDatabase::IconMappingEnumerator::GetNextIconMapping(IconMapping* r) {
+ if (!statement_.Step())
+ return false;
+
+ r->page_url = GURL(statement_.ColumnString(0));
+ r->icon_id = statement_.ColumnInt64(1);
+ return true;
+}
+
+URLDatabase::URLDatabase()
+ : has_keyword_search_terms_(false) {
+}
+
+URLDatabase::~URLDatabase() {
+}
+
+// static
+std::string URLDatabase::GURLToDatabaseURL(const GURL& gurl) {
+ // TODO(brettw): do something fancy here with encoding, etc.
+
+ // Strip username and password from URL before sending to DB.
+ GURL::Replacements replacements;
+ replacements.ClearUsername();
+ replacements.ClearPassword();
+
+ return (gurl.ReplaceComponents(replacements)).spec();
+}
+
+// Convenience to fill a history::URLRow. Must be in sync with the fields in
+// kURLRowFields.
+void URLDatabase::FillURLRow(sql::Statement& s, history::URLRow* i) {
+ DCHECK(i);
+ i->id_ = s.ColumnInt64(0);
+ i->url_ = GURL(s.ColumnString(1));
+ i->title_ = s.ColumnString16(2);
+ i->visit_count_ = s.ColumnInt(3);
+ i->typed_count_ = s.ColumnInt(4);
+ i->last_visit_ = base::Time::FromInternalValue(s.ColumnInt64(5));
+ i->hidden_ = s.ColumnInt(6) != 0;
+}
+
+bool URLDatabase::GetURLRow(URLID url_id, URLRow* info) {
+ // TODO(brettw) We need check for empty URLs to handle the case where
+ // there are old URLs in the database that are empty that got in before
+ // we added any checks. We should eventually be able to remove it
+ // when all inputs are using GURL (which prohibit empty input).
+ sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE id=?"));
+ statement.BindInt64(0, url_id);
+
+ if (statement.Step()) {
+ FillURLRow(statement, info);
+ return true;
+ }
+ return false;
+}
+
+bool URLDatabase::GetAllTypedUrls(URLRows* urls) {
+ sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE typed_count > 0"));
+
+ while (statement.Step()) {
+ URLRow info;
+ FillURLRow(statement, &info);
+ urls->push_back(info);
+ }
+ return true;
+}
+
+URLID URLDatabase::GetRowForURL(const GURL& url, history::URLRow* info) {
+ sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE url=?"));
+ std::string url_string = GURLToDatabaseURL(url);
+ statement.BindString(0, url_string);
+
+ if (!statement.Step())
+ return 0; // no data
+
+ if (info)
+ FillURLRow(statement, info);
+ return statement.ColumnInt64(0);
+}
+
+bool URLDatabase::UpdateURLRow(URLID url_id,
+ const history::URLRow& info) {
+ sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "UPDATE urls SET title=?,visit_count=?,typed_count=?,last_visit_time=?,"
+ "hidden=?"
+ "WHERE id=?"));
+ statement.BindString16(0, info.title());
+ statement.BindInt(1, info.visit_count());
+ statement.BindInt(2, info.typed_count());
+ statement.BindInt64(3, info.last_visit().ToInternalValue());
+ statement.BindInt(4, info.hidden() ? 1 : 0);
+ statement.BindInt64(5, url_id);
+
+ return statement.Run();
+}
+
+URLID URLDatabase::AddURLInternal(const history::URLRow& info,
+ bool is_temporary) {
+ // This function is used to insert into two different tables, so we have to
+ // do some shuffling. Unfortinately, we can't use the macro
+ // HISTORY_URL_ROW_FIELDS because that specifies the table name which is
+ // invalid in the insert syntax.
+ #define ADDURL_COMMON_SUFFIX \
+ " (url, title, visit_count, typed_count, "\
+ "last_visit_time, hidden) "\
+ "VALUES (?,?,?,?,?,?)"
+ const char* statement_name;
+ const char* statement_sql;
+ if (is_temporary) {
+ statement_name = "AddURLTemporary";
+ statement_sql = "INSERT INTO temp_urls" ADDURL_COMMON_SUFFIX;
+ } else {
+ statement_name = "AddURL";
+ statement_sql = "INSERT INTO urls" ADDURL_COMMON_SUFFIX;
+ }
+ #undef ADDURL_COMMON_SUFFIX
+
+ sql::Statement statement(GetDB().GetCachedStatement(
+ sql::StatementID(statement_name), statement_sql));
+ statement.BindString(0, GURLToDatabaseURL(info.url()));
+ statement.BindString16(1, info.title());
+ statement.BindInt(2, info.visit_count());
+ statement.BindInt(3, info.typed_count());
+ statement.BindInt64(4, info.last_visit().ToInternalValue());
+ statement.BindInt(5, info.hidden() ? 1 : 0);
+
+ if (!statement.Run()) {
+ VLOG(0) << "Failed to add url " << info.url().possibly_invalid_spec()
+ << " to table history.urls.";
+ return 0;
+ }
+ return GetDB().GetLastInsertRowId();
+}
+
+bool URLDatabase::DeleteURLRow(URLID id) {
+ sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "DELETE FROM urls WHERE id = ?"));
+ statement.BindInt64(0, id);
+
+ if (!statement.Run())
+ return false;
+
+ // And delete any keyword visits.
+ if (!has_keyword_search_terms_)
+ return true;
+
+ sql::Statement del_keyword_visit(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "DELETE FROM keyword_search_terms WHERE url_id=?"));
+ del_keyword_visit.BindInt64(0, id);
+
+ return del_keyword_visit.Run();
+}
+
+bool URLDatabase::CreateTemporaryURLTable() {
+ return CreateURLTable(true);
+}
+
+bool URLDatabase::CommitTemporaryURLTable() {
+ // See the comments in the header file as well as
+ // HistoryBackend::DeleteAllHistory() for more information on how this works
+ // and why it does what it does.
+ //
+ // Note that the main database overrides this to additionally create the
+ // supplimentary indices that the archived database doesn't need.
+
+ // Swap the url table out and replace it with the temporary one.
+ if (!GetDB().Execute("DROP TABLE urls")) {
+ NOTREACHED() << GetDB().GetErrorMessage();
+ return false;
+ }
+ if (!GetDB().Execute("ALTER TABLE temp_urls RENAME TO urls")) {
+ NOTREACHED() << GetDB().GetErrorMessage();
+ return false;
+ }
+
+ // Create the index over URLs. This is needed for the main, in-memory, and
+ // archived databases, so we always do it. The supplimentary indices used by
+ // the main database are not created here. When deleting all history, they
+ // are created by HistoryDatabase::RecreateAllButStarAndURLTables().
+ CreateMainURLIndex();
+
+ return true;
+}
+
+bool URLDatabase::InitURLEnumeratorForEverything(URLEnumerator* enumerator) {
+ DCHECK(!enumerator->initialized_);
+ std::string sql("SELECT ");
+ sql.append(kURLRowFields);
+ sql.append(" FROM urls");
+ enumerator->statement_.Assign(GetDB().GetUniqueStatement(sql.c_str()));
+ enumerator->initialized_ = enumerator->statement_.is_valid();
+ return enumerator->statement_.is_valid();
+}
+
+bool URLDatabase::InitURLEnumeratorForSignificant(URLEnumerator* enumerator) {
+ DCHECK(!enumerator->initialized_);
+ std::string sql("SELECT ");
+ sql.append(kURLRowFields);
+ sql.append(" FROM urls WHERE last_visit_time >= ? OR visit_count >= ? OR "
+ "typed_count >= ?");
+ enumerator->statement_.Assign(GetDB().GetUniqueStatement(sql.c_str()));
+ enumerator->statement_.BindInt64(
+ 0, AutocompleteAgeThreshold().ToInternalValue());
+ enumerator->statement_.BindInt(1, kLowQualityMatchVisitLimit);
+ enumerator->statement_.BindInt(2, kLowQualityMatchTypedLimit);
+ enumerator->initialized_ = enumerator->statement_.is_valid();
+ return enumerator->statement_.is_valid();
+}
+
+bool URLDatabase::InitIconMappingEnumeratorForEverything(
+ IconMappingEnumerator* enumerator) {
+ DCHECK(!enumerator->initialized_);
+ enumerator->statement_.Assign(GetDB().GetUniqueStatement(
+ "SELECT url, favicon_id FROM urls WHERE favicon_id <> 0"));
+ enumerator->initialized_ = enumerator->statement_.is_valid();
+ return enumerator->statement_.is_valid();
+}
+
+bool URLDatabase::AutocompleteForPrefix(const std::string& prefix,
+ size_t max_results,
+ bool typed_only,
+ URLRows* results) {
+ // NOTE: this query originally sorted by starred as the second parameter. But
+ // as bookmarks is no longer part of the db we no longer include the order
+ // by clause.
+ results->clear();
+ const char* sql;
+ int line;
+ if (typed_only) {
+ sql = "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls "
+ "WHERE url >= ? AND url < ? AND hidden = 0 AND typed_count > 0 "
+ "ORDER BY typed_count DESC, visit_count DESC, last_visit_time DESC "
+ "LIMIT ?";
+ line = __LINE__;
+ } else {
+ sql = "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls "
+ "WHERE url >= ? AND url < ? AND hidden = 0 "
+ "ORDER BY typed_count DESC, visit_count DESC, last_visit_time DESC "
+ "LIMIT ?";
+ line = __LINE__;
+ }
+ sql::Statement statement(
+ GetDB().GetCachedStatement(sql::StatementID(__FILE__, line), sql));
+
+ // We will find all strings between "prefix" and this string, which is prefix
+ // followed by the maximum character size. Use 8-bit strings for everything
+ // so we can be sure sqlite is comparing everything in 8-bit mode. Otherwise,
+ // it will have to convert strings either to UTF-8 or UTF-16 for comparison.
+ std::string end_query(prefix);
+ end_query.push_back(std::numeric_limits<unsigned char>::max());
+
+ statement.BindString(0, prefix);
+ statement.BindString(1, end_query);
+ statement.BindInt(2, static_cast<int>(max_results));
+
+ while (statement.Step()) {
+ history::URLRow info;
+ FillURLRow(statement, &info);
+ if (info.url().is_valid())
+ results->push_back(info);
+ }
+ return !results->empty();
+}
+
+bool URLDatabase::IsTypedHost(const std::string& host) {
+ const char* schemes[] = {
+ chrome::kHttpScheme,
+ chrome::kHttpsScheme,
+ chrome::kFtpScheme
+ };
+ URLRows dummy;
+ for (size_t i = 0; i < arraysize(schemes); ++i) {
+ std::string scheme_and_host(schemes[i]);
+ scheme_and_host += content::kStandardSchemeSeparator + host;
+ if (AutocompleteForPrefix(scheme_and_host + '/', 1, true, &dummy) ||
+ AutocompleteForPrefix(scheme_and_host + ':', 1, true, &dummy))
+ return true;
+ }
+ return false;
+}
+
+bool URLDatabase::FindShortestURLFromBase(const std::string& base,
+ const std::string& url,
+ int min_visits,
+ int min_typed,
+ bool allow_base,
+ history::URLRow* info) {
+ // Select URLs that start with |base| and are prefixes of |url|. All parts
+ // of this query except the substr() call can be done using the index. We
+ // could do this query with a couple of LIKE or GLOB statements as well, but
+ // those wouldn't use the index, and would run into problems with "wildcard"
+ // characters that appear in URLs (% for LIKE, or *, ? for GLOB).
+ std::string sql("SELECT ");
+ sql.append(kURLRowFields);
+ sql.append(" FROM urls WHERE url ");
+ sql.append(allow_base ? ">=" : ">");
+ sql.append(" ? AND url < :end AND url = substr(:end, 1, length(url)) "
+ "AND hidden = 0 AND visit_count >= ? AND typed_count >= ? "
+ "ORDER BY url LIMIT 1");
+ sql::Statement statement(GetDB().GetUniqueStatement(sql.c_str()));
+ statement.BindString(0, base);
+ statement.BindString(1, url); // :end
+ statement.BindInt(2, min_visits);
+ statement.BindInt(3, min_typed);
+
+ if (!statement.Step())
+ return false;
+
+ DCHECK(info);
+ FillURLRow(statement, info);
+ return true;
+}
+
+bool URLDatabase::InitKeywordSearchTermsTable() {
+ has_keyword_search_terms_ = true;
+ if (!GetDB().DoesTableExist("keyword_search_terms")) {
+ if (!GetDB().Execute("CREATE TABLE keyword_search_terms ("
+ "keyword_id INTEGER NOT NULL," // ID of the TemplateURL.
+ "url_id INTEGER NOT NULL," // ID of the url.
+ "lower_term LONGVARCHAR NOT NULL," // The search term, in lower case.
+ "term LONGVARCHAR NOT NULL)")) // The actual search term.
+ return false;
+ }
+ return true;
+}
+
+bool URLDatabase::CreateKeywordSearchTermsIndices() {
+ // For searching.
+ if (!GetDB().Execute(
+ "CREATE INDEX IF NOT EXISTS keyword_search_terms_index1 ON "
+ "keyword_search_terms (keyword_id, lower_term)")) {
+ return false;
+ }
+
+ // For deletion.
+ if (!GetDB().Execute(
+ "CREATE INDEX IF NOT EXISTS keyword_search_terms_index2 ON "
+ "keyword_search_terms (url_id)")) {
+ return false;
+ }
+
+ // For query or deletion by term.
+ if (!GetDB().Execute(
+ "CREATE INDEX IF NOT EXISTS keyword_search_terms_index3 ON "
+ "keyword_search_terms (term)")) {
+ return false;
+ }
+ return true;
+}
+
+bool URLDatabase::DropKeywordSearchTermsTable() {
+ // This will implicitly delete the indices over the table.
+ return GetDB().Execute("DROP TABLE keyword_search_terms");
+}
+
+bool URLDatabase::SetKeywordSearchTermsForURL(URLID url_id,
+ TemplateURLID keyword_id,
+ const string16& term) {
+ DCHECK(url_id && keyword_id && !term.empty());
+
+ sql::Statement exist_statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "SELECT term FROM keyword_search_terms "
+ "WHERE keyword_id = ? AND url_id = ?"));
+ exist_statement.BindInt64(0, keyword_id);
+ exist_statement.BindInt64(1, url_id);
+
+ if (exist_statement.Step())
+ return true; // Term already exists, no need to add it.
+
+ if (!exist_statement.Succeeded())
+ return false;
+
+ sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "INSERT INTO keyword_search_terms (keyword_id, url_id, lower_term, term) "
+ "VALUES (?,?,?,?)"));
+ statement.BindInt64(0, keyword_id);
+ statement.BindInt64(1, url_id);
+ statement.BindString16(2, base::i18n::ToLower(term));
+ statement.BindString16(3, term);
+ return statement.Run();
+}
+
+bool URLDatabase::GetKeywordSearchTermRow(URLID url_id,
+ KeywordSearchTermRow* row) {
+ DCHECK(url_id);
+ sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "SELECT keyword_id, term FROM keyword_search_terms WHERE url_id=?"));
+ statement.BindInt64(0, url_id);
+
+ if (!statement.Step())
+ return false;
+
+ if (row) {
+ row->url_id = url_id;
+ row->keyword_id = statement.ColumnInt64(0);
+ row->term = statement.ColumnString16(1);
+ }
+ return true;
+}
+
+bool URLDatabase::GetKeywordSearchTermRows(
+ const string16& term,
+ std::vector<KeywordSearchTermRow>* rows) {
+ sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "SELECT keyword_id, url_id FROM keyword_search_terms WHERE term=?"));
+ statement.BindString16(0, term);
+
+ if (!statement.is_valid())
+ return false;
+
+ while (statement.Step()) {
+ KeywordSearchTermRow row;
+ row.url_id = statement.ColumnInt64(1);
+ row.keyword_id = statement.ColumnInt64(0);
+ row.term = term;
+ rows->push_back(row);
+ }
+ return true;
+}
+
+void URLDatabase::DeleteAllSearchTermsForKeyword(
+ TemplateURLID keyword_id) {
+ DCHECK(keyword_id);
+ sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "DELETE FROM keyword_search_terms WHERE keyword_id=?"));
+ statement.BindInt64(0, keyword_id);
+
+ statement.Run();
+}
+
+void URLDatabase::GetMostRecentKeywordSearchTerms(
+ TemplateURLID keyword_id,
+ const string16& prefix,
+ int max_count,
+ std::vector<KeywordSearchTermVisit>* matches) {
+ // NOTE: the keyword_id can be zero if on first run the user does a query
+ // before the TemplateURLService has finished loading. As the chances of this
+ // occurring are small, we ignore it.
+ if (!keyword_id)
+ return;
+
+ DCHECK(!prefix.empty());
+ sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "SELECT DISTINCT kv.term, u.visit_count, u.last_visit_time "
+ "FROM keyword_search_terms kv "
+ "JOIN urls u ON kv.url_id = u.id "
+ "WHERE kv.keyword_id = ? AND kv.lower_term >= ? AND kv.lower_term < ? "
+ "ORDER BY u.last_visit_time DESC LIMIT ?"));
+
+ // NOTE: Keep this ToLower() call in sync with search_provider.cc.
+ string16 lower_prefix = base::i18n::ToLower(prefix);
+ // This magic gives us a prefix search.
+ string16 next_prefix = lower_prefix;
+ next_prefix[next_prefix.size() - 1] =
+ next_prefix[next_prefix.size() - 1] + 1;
+ statement.BindInt64(0, keyword_id);
+ statement.BindString16(1, lower_prefix);
+ statement.BindString16(2, next_prefix);
+ statement.BindInt(3, max_count);
+
+ KeywordSearchTermVisit visit;
+ while (statement.Step()) {
+ visit.term = statement.ColumnString16(0);
+ visit.visits = statement.ColumnInt(1);
+ visit.time = base::Time::FromInternalValue(statement.ColumnInt64(2));
+ matches->push_back(visit);
+ }
+}
+
+bool URLDatabase::DeleteKeywordSearchTerm(const string16& term) {
+ sql::Statement statement(GetDB().GetCachedStatement(SQL_FROM_HERE,
+ "DELETE FROM keyword_search_terms WHERE term=?"));
+ statement.BindString16(0, term);
+
+ return statement.Run();
+}
+
+bool URLDatabase::DropStarredIDFromURLs() {
+ if (!GetDB().DoesColumnExist("urls", "starred_id"))
+ return true; // urls is already updated, no need to continue.
+
+ // Create a temporary table to contain the new URLs table.
+ if (!CreateTemporaryURLTable()) {
+ NOTREACHED();
+ return false;
+ }
+
+ // Copy the contents.
+ if (!GetDB().Execute(
+ "INSERT INTO temp_urls (id, url, title, visit_count, typed_count, "
+ "last_visit_time, hidden, favicon_id) "
+ "SELECT id, url, title, visit_count, typed_count, last_visit_time, "
+ "hidden, favicon_id FROM urls")) {
+ NOTREACHED() << GetDB().GetErrorMessage();
+ return false;
+ }
+
+ // Rename/commit the tmp table.
+ CommitTemporaryURLTable();
+
+ return true;
+}
+
+bool URLDatabase::CreateURLTable(bool is_temporary) {
+ const char* name = is_temporary ? "temp_urls" : "urls";
+ if (GetDB().DoesTableExist(name))
+ return true;
+
+ std::string sql;
+ sql.append("CREATE TABLE ");
+ sql.append(name);
+ sql.append("("
+ "id INTEGER PRIMARY KEY,"
+ "url LONGVARCHAR,"
+ "title LONGVARCHAR,"
+ "visit_count INTEGER DEFAULT 0 NOT NULL,"
+ "typed_count INTEGER DEFAULT 0 NOT NULL,"
+ "last_visit_time INTEGER NOT NULL,"
+ "hidden INTEGER DEFAULT 0 NOT NULL,"
+ "favicon_id INTEGER DEFAULT 0 NOT NULL)"); // favicon_id is not used now.
+
+ return GetDB().Execute(sql.c_str());
+}
+
+bool URLDatabase::CreateMainURLIndex() {
+ // Index over URLs so we can quickly look up based on URL.
+ return GetDB().Execute(
+ "CREATE INDEX IF NOT EXISTS urls_url_index ON urls (url)");
+}
+
+} // namespace history