diff options
author | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
---|---|---|
committer | initial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-07-26 23:55:29 +0000 |
commit | 09911bf300f1a419907a9412154760efd0b7abc3 (patch) | |
tree | f131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/browser/history/url_database.cc | |
parent | 586acc5fe142f498261f52c66862fa417c3d52d2 (diff) | |
download | chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.zip chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.gz chromium_src-09911bf300f1a419907a9412154760efd0b7abc3.tar.bz2 |
Add chrome to the repository.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/history/url_database.cc')
-rw-r--r-- | chrome/browser/history/url_database.cc | 485 |
1 files changed, 485 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..0ce8072 --- /dev/null +++ b/chrome/browser/history/url_database.cc @@ -0,0 +1,485 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "chrome/browser/history/url_database.h" + +#include <algorithm> +#include <limits> + +#include "base/string_util.h" +#include "chrome/common/l10n_util.h" +#include "chrome/common/sqlite_utils.h" +#include "googleurl/src/gurl.h" + +namespace history { + +const char URLDatabase::kURLRowFields[] = HISTORY_URL_ROW_FIELDS; +const int URLDatabase::kNumURLRowFields = 9; + +bool URLDatabase::URLEnumerator::GetNextURL(URLRow* r) { + if (statement_.step() == SQLITE_ROW) { + FillURLRow(statement_, r); + return true; + } + return false; +} + +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. + return gurl.spec(); +} + +// Convenience to fill a history::URLRow. Must be in sync with the fields in +// kURLRowFields. +void URLDatabase::FillURLRow(SQLStatement& s, history::URLRow* i) { + DCHECK(i); + i->id_ = s.column_int64(0); + i->url_ = GURL(s.column_text(1)); + i->title_.assign(s.column_text16(2)); + i->visit_count_ = s.column_int(3); + i->typed_count_ = s.column_int(4); + i->last_visit_ = Time::FromInternalValue(s.column_int64(5)); + i->hidden_ = s.column_int(6) != 0; + i->favicon_id_ = s.column_int64(7); + i->star_id_ = s.column_int64(8); +} + +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). + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE id=?"); + if (!statement.is_valid()) + return false; + + statement->bind_int64(0, url_id); + if (statement->step() == SQLITE_ROW) { + FillURLRow(*statement, info); + return true; + } + return false; +} + +URLID URLDatabase::GetRowForURL(const GURL& url, history::URLRow* info) { + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls WHERE url=?"); + if (!statement.is_valid()) + return 0; + + std::string url_string = GURLToDatabaseURL(url); + statement->bind_string(0, url_string); + if (statement->step() != SQLITE_ROW) + return 0; // no data + + if (info) + FillURLRow(*statement, info); + return statement->column_int64(0); +} + +bool URLDatabase::UpdateURLRow(URLID url_id, + const history::URLRow& info) { + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "UPDATE urls SET title=?,visit_count=?,typed_count=?,last_visit_time=?," + "hidden=?,starred_id=?,favicon_id=?" + "WHERE id=?"); + if (!statement.is_valid()) + return false; + + statement->bind_wstring(0, info.title()); + statement->bind_int(1, info.visit_count()); + statement->bind_int(2, info.typed_count()); + statement->bind_int64(3, info.last_visit().ToInternalValue()); + statement->bind_int(4, info.hidden() ? 1 : 0); + statement->bind_int64(5, info.star_id()); + statement->bind_int64(6, info.favicon_id()); + statement->bind_int64(7, url_id); + return statement->step() == SQLITE_DONE; +} + +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,starred_id,favicon_id)"\ + "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 + + SqliteCompiledStatement statement(statement_name, 0, GetStatementCache(), + statement_sql); + if (!statement.is_valid()) + return 0; + + statement->bind_string(0, GURLToDatabaseURL(info.url())); + statement->bind_wstring(1, info.title()); + statement->bind_int(2, info.visit_count()); + statement->bind_int(3, info.typed_count()); + statement->bind_int64(4, info.last_visit().ToInternalValue()); + statement->bind_int(5, info.hidden() ? 1 : 0); + statement->bind_int64(6, info.star_id()); + statement->bind_int64(7, info.favicon_id()); + + if (statement->step() != SQLITE_DONE) + return 0; + return sqlite3_last_insert_rowid(GetDB()); +} + +bool URLDatabase::DeleteURLRow(URLID id) { + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "DELETE FROM urls WHERE id = ?"); + if (!statement.is_valid()) + return false; + + statement->bind_int64(0, id); + if (statement->step() != SQLITE_DONE) + return false; + + // And delete any keyword visits. + if (!has_keyword_search_terms_) + return true; + + SQLITE_UNIQUE_STATEMENT(del_keyword_visit, GetStatementCache(), + "DELETE FROM keyword_search_terms WHERE url_id=?"); + if (!del_keyword_visit.is_valid()) + return false; + del_keyword_visit->bind_int64(0, id); + return (del_keyword_visit->step() == SQLITE_DONE); +} + +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 (sqlite3_exec(GetDB(), "DROP TABLE urls", + NULL, NULL, NULL) != SQLITE_OK) { + NOTREACHED(); + return false; + } + if (sqlite3_exec(GetDB(), "ALTER TABLE temp_urls RENAME TO urls", + NULL, NULL, NULL) != SQLITE_OK) { + NOTREACHED(); + 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"); + if (enumerator->statement_.prepare(GetDB(), sql.c_str()) != SQLITE_OK) { + NOTREACHED() << "Query statement prep failed"; + return false; + } + enumerator->initialized_ = true; + return true; +} + +bool URLDatabase::IsFavIconUsed(FavIconID favicon_id) { + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT id FROM urls WHERE favicon_id=? LIMIT 1"); + if (!statement.is_valid()) + return false; + + statement->bind_int64(0, favicon_id); + return statement->step() == SQLITE_ROW; +} + +void URLDatabase::AutocompleteForPrefix(const std::wstring& prefix, + size_t max_results, + std::vector<history::URLRow>* results) { + results->clear(); + + // NOTE: Sorting by "starred_id == 0" is subtle. First we do the comparison, + // and, according to the SQLite docs, get a numeric result (all binary + // operators except "||" result in a numeric value), which is either 1 or 0 + // (implied by documentation showing the results of various comparison + // operations to always be 1 or 0). Now we sort ascending, meaning all 0s go + // first -- so, all URLs where "starred_id == 0" is 0, i.e. all starred URLs. + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT" HISTORY_URL_ROW_FIELDS "FROM urls " + "WHERE url >= ? AND url < ? AND hidden = 0 " + "ORDER BY typed_count DESC, starred_id == 0, visit_count DESC, " + "last_visit_time DESC " + "LIMIT ?"); + if (!statement.is_valid()) + return; + + // We will find all strings between "prefix" and this string, which is prefix + // followed by the maximum character size. + std::wstring end_query(prefix); + end_query.push_back(std::numeric_limits<wchar_t>::max()); + + statement->bind_wstring(0, prefix); + statement->bind_wstring(1, end_query); + statement->bind_int(2, static_cast<int>(max_results)); + + while (statement->step() == SQLITE_ROW) { + history::URLRow info; + FillURLRow(*statement, &info); + if (info.url().is_valid()) + results->push_back(info); + } +} + +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"); + SQLStatement statement; + if (statement.prepare(GetDB(), sql.c_str()) != SQLITE_OK) { + NOTREACHED() << "select statement prep failed"; + return false; + } + + statement.bind_string(0, base); + statement.bind_string(1, url); // :end + statement.bind_int(2, min_visits); + statement.bind_int(3, min_typed); + + if (statement.step() != SQLITE_ROW) + return false; + + DCHECK(info); + FillURLRow(statement, info); + return true; +} + +bool URLDatabase::InitKeywordSearchTermsTable() { + has_keyword_search_terms_ = true; + if (!DoesSqliteTableExist(GetDB(), "keyword_search_terms")) { + if (sqlite3_exec(GetDB(), "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. + NULL, NULL, NULL) != SQLITE_OK) + return false; + } + + // For searching. + sqlite3_exec(GetDB(), "CREATE INDEX keyword_search_terms_index1 ON " + "keyword_search_terms (keyword_id, lower_term)", + NULL, NULL, NULL); + + // For deletion. + sqlite3_exec(GetDB(), "CREATE INDEX keyword_search_terms_index2 ON " + "keyword_search_terms (url_id)", + NULL, NULL, NULL); + + return true; +} + +bool URLDatabase::DropKeywordSearchTermsTable() { + // This will implicitly delete the indices over the table. + return sqlite3_exec(GetDB(), "DROP TABLE keyword_search_terms", + NULL, NULL, NULL) == SQLITE_OK; +} + +bool URLDatabase::SetKeywordSearchTermsForURL(URLID url_id, + TemplateURL::IDType keyword_id, + const std::wstring& term) { + DCHECK(url_id && keyword_id && !term.empty()); + + SQLITE_UNIQUE_STATEMENT(exist_statement, GetStatementCache(), + "SELECT term FROM keyword_search_terms " + "WHERE keyword_id = ? AND url_id = ?"); + if (!exist_statement.is_valid()) + return false; + exist_statement->bind_int64(0, keyword_id); + exist_statement->bind_int64(1, url_id); + if (exist_statement->step() == SQLITE_ROW) + return true; // Term already exists, no need to add it. + + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "INSERT INTO keyword_search_terms (keyword_id, url_id, lower_term, term) " + "VALUES (?,?,?,?)"); + if (!statement.is_valid()) + return false; + + statement->bind_int64(0, keyword_id); + statement->bind_int64(1, url_id); + statement->bind_wstring(2, l10n_util::ToLower(term)); + statement->bind_wstring(3, term); + return (statement->step() == SQLITE_DONE); +} + +void URLDatabase::DeleteAllSearchTermsForKeyword( + TemplateURL::IDType keyword_id) { + DCHECK(keyword_id); + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "DELETE FROM keyword_search_terms WHERE keyword_id=?"); + if (!statement.is_valid()) + return; + + statement->bind_int64(0, keyword_id); + statement->step(); +} + +void URLDatabase::GetMostRecentKeywordSearchTerms( + TemplateURL::IDType keyword_id, + const std::wstring& 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 TemplateURLModel has finished loading. As the chances of this + // occurring are small, we ignore it. + if (!keyword_id) + return; + + DCHECK(!prefix.empty()); + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT DISTINCT kv.term, 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 ?"); + if (!statement.is_valid()) + return; + + // NOTE: Keep this ToLower() call in sync with search_provider.cc. + const std::wstring lower_prefix = l10n_util::ToLower(prefix); + // This magic gives us a prefix search. + std::wstring next_prefix = lower_prefix; + next_prefix[next_prefix.size() - 1] = + next_prefix[next_prefix.size() - 1] + 1; + statement->bind_int64(0, keyword_id); + statement->bind_wstring(1, lower_prefix); + statement->bind_wstring(2, next_prefix); + statement->bind_int(3, max_count); + + KeywordSearchTermVisit visit; + while (statement->step() == SQLITE_ROW) { + visit.term = statement->column_string16(0); + visit.time = Time::FromInternalValue(statement->column_int64(1)); + matches->push_back(visit); + } +} + +bool URLDatabase::MigrateFromVersion11ToVersion12() { + URLRow about_row; + if (GetRowForURL(GURL("about:blank"), &about_row)) { + about_row.set_favicon_id(0); + return UpdateURLRow(about_row.id(), about_row); + } + return true; +} + +bool URLDatabase::CreateURLTable(bool is_temporary) { + const char* name = is_temporary ? "temp_urls" : "urls"; + if (DoesSqliteTableExist(GetDB(), 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," + "starred_id INTEGER DEFAULT 0 NOT NULL)"); + + return sqlite3_exec(GetDB(), sql.c_str(), NULL, NULL, NULL) == SQLITE_OK; +} + +void URLDatabase::CreateMainURLIndex() { + // Index over URLs so we can quickly look up based on URL. Ignore errors as + // this likely already exists (and the same below). + sqlite3_exec(GetDB(), "CREATE INDEX urls_url_index ON urls (url)", NULL, NULL, + NULL); +} + +void URLDatabase::CreateSupplimentaryURLIndices() { + // Add a favicon index. This is useful when we delete urls. + sqlite3_exec(GetDB(), "CREATE INDEX urls_favicon_id_INDEX ON urls (favicon_id)", + NULL, NULL, NULL); + + // Index on starred_id. + sqlite3_exec(GetDB(), "CREATE INDEX urls_starred_id_INDEX ON urls " + "(starred_id)", NULL, NULL, NULL); +} + +} // namespace history |