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/visit_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/visit_database.cc')
-rw-r--r-- | chrome/browser/history/visit_database.cc | 392 |
1 files changed, 392 insertions, 0 deletions
diff --git a/chrome/browser/history/visit_database.cc b/chrome/browser/history/visit_database.cc new file mode 100644 index 0000000..5001ec1 --- /dev/null +++ b/chrome/browser/history/visit_database.cc @@ -0,0 +1,392 @@ +// 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 <algorithm> +#include <limits> +#include <map> +#include <set> + +#include "chrome/browser/history/visit_database.h" + +#include "chrome/browser/history/url_database.h" +#include "chrome/common/page_transition_types.h" + +// Rows, in order, of the visit table. +#define HISTORY_VISIT_ROW_FIELDS \ + " id,url,visit_time,from_visit,transition,segment_id,is_indexed " + +namespace history { + +VisitDatabase::VisitDatabase() { +} + +VisitDatabase::~VisitDatabase() { +} + +bool VisitDatabase::InitVisitTable() { + if (!DoesSqliteTableExist(GetDB(), "visits")) { + if (sqlite3_exec(GetDB(), "CREATE TABLE visits(" + "id INTEGER PRIMARY KEY," + "url INTEGER NOT NULL," // key of the URL this corresponds to + "visit_time INTEGER NOT NULL," + "from_visit INTEGER," + "transition INTEGER DEFAULT 0 NOT NULL," + "segment_id INTEGER," + "is_indexed BOOLEAN)", // True when we have indexed data for this visit. + NULL, NULL, NULL) != SQLITE_OK) + return false; + } else if (!DoesSqliteColumnExist(GetDB(), "visits", + "is_indexed", "BOOLEAN")) { + // Old versions don't have the is_indexed column, we can just add that and + // not worry about different database revisions, since old ones will + // continue to work. + // + // TODO(brettw) this should be removed once we think everybody has been + // updated (added early Mar 2008). + if (sqlite3_exec(GetDB(), + "ALTER TABLE visits ADD COLUMN is_indexed BOOLEAN", + NULL, NULL, NULL) != SQLITE_OK) + return false; + } + + // Index over url so we can quickly find visits for a page. This will just + // fail if it already exists and we'll ignore it. + sqlite3_exec(GetDB(), "CREATE INDEX visits_url_index ON visits (url)", + NULL, NULL, NULL); + + // Create an index over from visits so that we can efficiently find + // referrers and redirects. Ignore failures because it likely already exists. + sqlite3_exec(GetDB(), "CREATE INDEX visits_from_index ON visits (from_visit)", + NULL, NULL, NULL); + + // Create an index over time so that we can efficiently find the visits in a + // given time range (most history views are time-based). Ignore failures + // because it likely already exists. + sqlite3_exec(GetDB(), "CREATE INDEX visits_time_index ON visits (visit_time)", + NULL, NULL, NULL); + + return true; +} + +bool VisitDatabase::DropVisitTable() { + // This will also drop the indices over the table. + return sqlite3_exec(GetDB(), "DROP TABLE visits", NULL, NULL, NULL) == + SQLITE_OK; +} + +// Must be in sync with HISTORY_VISIT_ROW_FIELDS. +// static +void VisitDatabase::FillVisitRow(SQLStatement& statement, VisitRow* visit) { + visit->visit_id = statement.column_int64(0); + visit->url_id = statement.column_int64(1); + visit->visit_time = Time::FromInternalValue(statement.column_int64(2)); + visit->referring_visit = statement.column_int64(3); + visit->transition = PageTransition::FromInt(statement.column_int(4)); + visit->segment_id = statement.column_int64(5); + visit->is_indexed = !!statement.column_int(6); +} + +// static +void VisitDatabase::FillVisitVector(SQLStatement& statement, + VisitVector* visits) { + while (statement.step() == SQLITE_ROW) { + history::VisitRow visit; + FillVisitRow(statement, &visit); + visits->push_back(visit); + } +} + +VisitID VisitDatabase::AddVisit(VisitRow* visit) { + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "INSERT INTO visits(" + "url,visit_time,from_visit,transition,segment_id,is_indexed)" + "VALUES(?,?,?,?,?,?)"); + if (!statement.is_valid()) + return 0; + + statement->bind_int64(0, visit->url_id); + statement->bind_int64(1, visit->visit_time.ToInternalValue()); + statement->bind_int64(2, visit->referring_visit); + statement->bind_int64(3, visit->transition); + statement->bind_int64(4, visit->segment_id); + statement->bind_int64(5, visit->is_indexed); + if (statement->step() != SQLITE_DONE) + return 0; + + visit->visit_id = sqlite3_last_insert_rowid(GetDB()); + return visit->visit_id; +} + +void VisitDatabase::DeleteVisit(const VisitRow& visit) { + // Patch around this visit. Any visits that this went to will now have their + // "source" be the deleted visit's source. + SQLITE_UNIQUE_STATEMENT(update_chain, GetStatementCache(), + "UPDATE visits SET from_visit=? " + "WHERE from_visit=?"); + if (!update_chain.is_valid()) + return; + update_chain->bind_int64(0, visit.referring_visit); + update_chain->bind_int64(1, visit.visit_id); + update_chain->step(); + + // Now delete the actual visit. + SQLITE_UNIQUE_STATEMENT(del, GetStatementCache(), + "DELETE FROM visits WHERE id=?"); + if (!del.is_valid()) + return; + del->bind_int64(0, visit.visit_id); + del->step(); +} + +bool VisitDatabase::GetRowForVisit(VisitID visit_id, VisitRow* out_visit) { + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits WHERE id=?"); + if (!statement.is_valid()) + return false; + + statement->bind_int64(0, visit_id); + if (statement->step() != SQLITE_ROW) + return false; + + FillVisitRow(*statement, out_visit); + return true; +} + +bool VisitDatabase::UpdateVisitRow(const VisitRow& visit) { + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "UPDATE visits SET " + "url=?,visit_time=?,from_visit=?,transition=?,segment_id=?,is_indexed=? " + "WHERE id=?"); + if (!statement.is_valid()) + return false; + + statement->bind_int64(0, visit.url_id); + statement->bind_int64(1, visit.visit_time.ToInternalValue()); + statement->bind_int64(2, visit.referring_visit); + statement->bind_int64(3, visit.transition); + statement->bind_int64(4, visit.segment_id); + statement->bind_int64(5, visit.is_indexed); + statement->bind_int64(6, visit.visit_id); + return statement->step() == SQLITE_DONE; +} + +bool VisitDatabase::GetVisitsForURL(URLID url_id, VisitVector* visits) { + visits->clear(); + + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT" HISTORY_VISIT_ROW_FIELDS + "FROM visits " + "WHERE url=? " + "ORDER BY visit_time ASC"); + if (!statement.is_valid()) + return false; + + statement->bind_int64(0, url_id); + FillVisitVector(*statement, visits); + return true; +} + +void VisitDatabase::GetAllVisitsInRange(Time begin_time, Time end_time, + int max_results, + VisitVector* visits) { + visits->clear(); + + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits " + "WHERE visit_time >= ? AND visit_time < ?" + "ORDER BY visit_time LIMIT ?"); + if (!statement.is_valid()) + return; + + // See GetVisibleVisitsInRange for more info on how these times are bound. + int64 end = end_time.ToInternalValue(); + statement->bind_int64(0, begin_time.ToInternalValue()); + statement->bind_int64(1, end ? end : std::numeric_limits<int64>::max()); + statement->bind_int64(2, + max_results ? max_results : std::numeric_limits<int64>::max()); + + FillVisitVector(*statement, visits); +} + +void VisitDatabase::GetVisibleVisitsInRange(Time begin_time, Time end_time, + bool most_recent_visit_only, + int max_count, + VisitVector* visits) { + visits->clear(); + // The visit_time values can be duplicated in a redirect chain, so we sort + // by id too, to ensure a consistent ordering just in case. + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits " + "WHERE visit_time >= ? AND visit_time < ? " + "AND (transition & ?) != 0 " // CHAIN_END + "AND (transition & ?) NOT IN (?, ?) " // NO SUBFRAME + "ORDER BY visit_time DESC, id DESC"); + if (!statement.is_valid()) + return; + + // Note that we use min/max values for querying unlimited ranges of time using + // the same statement. Since the time has an index, this will be about the + // same amount of work as just doing a query for everything with no qualifier. + int64 end = end_time.ToInternalValue(); + statement->bind_int64(0, begin_time.ToInternalValue()); + statement->bind_int64(1, end ? end : std::numeric_limits<int64>::max()); + statement->bind_int(2, PageTransition::CHAIN_END); + statement->bind_int(3, PageTransition::CORE_MASK); + statement->bind_int(4, PageTransition::AUTO_SUBFRAME); + statement->bind_int(5, PageTransition::MANUAL_SUBFRAME); + + std::set<URLID> found_urls; + while (statement->step() == SQLITE_ROW) { + VisitRow visit; + FillVisitRow(*statement, &visit); + if (most_recent_visit_only) { + // Make sure the URL this visit corresponds to is unique if required. + if (found_urls.find(visit.url_id) != found_urls.end()) + continue; + found_urls.insert(visit.url_id); + } + visits->push_back(visit); + + if (max_count > 0 && static_cast<int>(visits->size()) >= max_count) + break; + } +} + +VisitID VisitDatabase::GetMostRecentVisitForURL(URLID url_id, + VisitRow* visit_row) { + // The visit_time values can be duplicated in a redirect chain, so we sort + // by id too, to ensure a consistent ordering just in case. + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT" HISTORY_VISIT_ROW_FIELDS "FROM visits " + "WHERE url=? " + "ORDER BY visit_time DESC, id DESC " + "LIMIT 1"); + if (!statement.is_valid()) + return 0; + + statement->bind_int64(0, url_id); + if (statement->step() != SQLITE_ROW) + return 0; // No visits for this URL. + + if (visit_row) { + FillVisitRow(*statement, visit_row); + return visit_row->visit_id; + } + return statement->column_int64(0); +} + +bool VisitDatabase::GetMostRecentVisitsForURL(URLID url_id, + int max_results, + VisitVector* visits) { + visits->clear(); + + // The visit_time values can be duplicated in a redirect chain, so we sort + // by id too, to ensure a consistent ordering just in case. + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT" HISTORY_VISIT_ROW_FIELDS + "FROM visits " + "WHERE url=? " + "ORDER BY visit_time DESC, id DESC " + "LIMIT ?"); + if (!statement.is_valid()) + return false; + + statement->bind_int64(0, url_id); + statement->bind_int(1, max_results); + FillVisitVector(*statement, visits); + return true; +} + +bool VisitDatabase::GetRedirectFromVisit(VisitID from_visit, + VisitID* to_visit, + GURL* to_url) { + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT v.id,u.url " + "FROM visits v JOIN urls u ON v.url = u.id " + "WHERE v.from_visit = ? " + "AND (v.transition & ?) != 0"); // IS_REDIRECT_MASK + if (!statement.is_valid()) + return false; + + statement->bind_int64(0, from_visit); + statement->bind_int(1, PageTransition::IS_REDIRECT_MASK); + + if (statement->step() != SQLITE_ROW) + return false; // No redirect from this visit. + if (to_visit) + *to_visit = statement->column_int64(0); + if (to_url) + *to_url = GURL(statement->column_string(1)); + return true; +} + +bool VisitDatabase::GetVisitCountToHost(const GURL& url, + int* count, + Time* first_visit) { + if (!url.SchemeIs("http") && !url.SchemeIs("https")) + return false; + + // We need to search for URLs with a matching host/port. One way to query for + // this is to use the LIKE operator, eg 'url LIKE http://google.com/%'. This + // is inefficient though in that it doesn't use the index and each entry must + // be visited. The same query can be executed by using >= and < operator. + // The query becomes: + // 'url >= http://google.com/' and url < http://google.com0'. + // 0 is used as it is one character greater than '/'. + GURL search_url(url); + const std::string host_query_min = search_url.GetOrigin().spec(); + + if (host_query_min.empty()) + return false; + + std::string host_query_max = host_query_min; + host_query_max[host_query_max.size() - 1] = '0'; + + SQLITE_UNIQUE_STATEMENT(statement, GetStatementCache(), + "SELECT MIN(v.visit_time), COUNT(*) " + "FROM visits v INNER JOIN urls u ON v.url = u.id " + "WHERE (u.url >= ? AND u.url < ?)"); + if (!statement.is_valid()) + return false; + + statement->bind_string(0, host_query_min); + statement->bind_string(1, host_query_max); + + if (statement->step() != SQLITE_ROW) { + // We've never been to this page before. + *count = 0; + return true; + } + + *first_visit = Time::FromInternalValue(statement->column_int64(0)); + *count = statement->column_int(1); + return true; +} + +} // namespace history |