summaryrefslogtreecommitdiffstats
path: root/chrome/browser/history/visit_database.cc
diff options
context:
space:
mode:
authorinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 23:55:29 +0000
committerinitial.commit <initial.commit@0039d316-1c4b-4281-b951-d872f2087c98>2008-07-26 23:55:29 +0000
commit09911bf300f1a419907a9412154760efd0b7abc3 (patch)
treef131325fb4e2ad12c6d3504ab75b16dd92facfed /chrome/browser/history/visit_database.cc
parent586acc5fe142f498261f52c66862fa417c3d52d2 (diff)
downloadchromium_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.cc392
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