--- a/browser/components/places/tests/browser/browser_markPageAsFollowedLink.js
+++ b/browser/components/places/tests/browser/browser_markPageAsFollowedLink.js
@@ -53,15 +53,16 @@ add_task(function* test() {
function* getTransitionForUrl(url) {
// Ensure all the transactions completed.
yield PlacesTestUtils.promiseAsyncUpdates();
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.execute(`
SELECT visit_type
FROM moz_historyvisits
- WHERE place_id = (SELECT id FROM moz_places WHERE url = :url)`,
+ JOIN moz_places h ON place_id = h.id
+ WHERE url_hash = hash(:url) AND url = :url`,
{ url });
if (rows.length) {
return rows[0].getResultByName("visit_type");
}
return null;
}
--- a/services/sync/modules/engines/bookmarks.js
+++ b/services/sync/modules/engines/bookmarks.js
@@ -1089,17 +1089,17 @@ BookmarksStore.prototype = {
.DBConnection;
return this._stmts[query] = db.createAsyncStatement(query);
},
get _frecencyStm() {
return this._getStmt(
"SELECT frecency " +
"FROM moz_places " +
- "WHERE url = :url " +
+ "WHERE url_hash = hash(:url) AND url = :url " +
"LIMIT 1");
},
_frecencyCols: ["frecency"],
get _setGUIDStm() {
return this._getStmt(
"UPDATE moz_bookmarks " +
"SET guid = :guid " +
--- a/services/sync/modules/engines/history.js
+++ b/services/sync/modules/engines/history.js
@@ -100,17 +100,17 @@ HistoryStore.prototype = {
.DBConnection;
return this._stmts[query] = db.createAsyncStatement(query);
},
get _setGUIDStm() {
return this._getStmt(
"UPDATE moz_places " +
"SET guid = :guid " +
- "WHERE url = :page_url");
+ "WHERE url_hash = hash(:page_url) AND url = :page_url");
},
// Some helper functions to handle GUIDs
setGUID: function setGUID(uri, guid) {
uri = uri.spec ? uri.spec : uri;
if (!guid) {
guid = Utils.makeGUID();
@@ -122,17 +122,17 @@ HistoryStore.prototype = {
Async.querySpinningly(stmt);
return guid;
},
get _guidStm() {
return this._getStmt(
"SELECT guid " +
"FROM moz_places " +
- "WHERE url = :page_url");
+ "WHERE url_hash = hash(:page_url) AND url = :page_url");
},
_guidCols: ["guid"],
GUIDForUri: function GUIDForUri(uri, create) {
let stm = this._guidStm;
stm.params.page_url = uri.spec ? uri.spec : uri;
// Use the existing GUID if it exists
@@ -141,22 +141,22 @@ HistoryStore.prototype = {
return result.guid;
// Give the uri a GUID if it doesn't have one
if (create)
return this.setGUID(uri);
},
get _visitStm() {
- return this._getStmt(
- "/* do not warn (bug 599936) */ " +
- "SELECT visit_type type, visit_date date " +
- "FROM moz_historyvisits " +
- "WHERE place_id = (SELECT id FROM moz_places WHERE url = :url) " +
- "ORDER BY date DESC LIMIT 20");
+ return this._getStmt(`/* do not warn (bug 599936) */
+ SELECT visit_type type, visit_date date
+ FROM moz_historyvisits
+ JOIN moz_places h ON h.id = place_id
+ WHERE url_hash = hash(:url) AND url = :url
+ ORDER BY date DESC LIMIT 20`);
},
_visitCols: ["date", "type"],
get _urlStm() {
return this._getStmt(
"SELECT url, title, frecency " +
"FROM moz_places " +
"WHERE guid = :guid");
--- a/services/sync/tests/unit/test_bookmark_invalid.js
+++ b/services/sync/tests/unit/test_bookmark_invalid.js
@@ -19,17 +19,17 @@ add_task(function* test_ignore_invalid_u
let bmid = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
Services.io.newURI("http://example.com/", null, null),
PlacesUtils.bookmarks.DEFAULT_INDEX,
"the title");
// Now update moz_places with an invalid url.
yield PlacesUtils.withConnectionWrapper("test_ignore_invalid_uri", Task.async(function* (db) {
yield db.execute(
- `UPDATE moz_places SET url = :url
+ `UPDATE moz_places SET url = :url, url_hash = hash(:url)
WHERE id = (SELECT b.fk FROM moz_bookmarks b
WHERE b.id = :id LIMIT 1)`,
{ id: bmid, url: "<invalid url>" });
}));
// Ensure that this doesn't throw even though the DB is now in a bad state (a
// bookmark has an illegal url).
engine._buildGUIDMap();
--- a/services/sync/tests/unit/test_history_store.js
+++ b/services/sync/tests/unit/test_history_store.js
@@ -184,26 +184,26 @@ add_test(function test_null_title() {
add_test(function test_invalid_records() {
_("Make sure we handle invalid URLs in places databases gracefully.");
let connection = PlacesUtils.history
.QueryInterface(Ci.nsPIPlacesDatabase)
.DBConnection;
let stmt = connection.createAsyncStatement(
"INSERT INTO moz_places "
- + "(url, title, rev_host, visit_count, last_visit_date) "
- + "VALUES ('invalid-uri', 'Invalid URI', '.', 1, " + TIMESTAMP3 + ")"
+ + "(url, url_hash, title, rev_host, visit_count, last_visit_date) "
+ + "VALUES ('invalid-uri', hash('invalid-uri'), 'Invalid URI', '.', 1, " + TIMESTAMP3 + ")"
);
Async.querySpinningly(stmt);
stmt.finalize();
// Add the corresponding visit to retain database coherence.
stmt = connection.createAsyncStatement(
"INSERT INTO moz_historyvisits "
+ "(place_id, visit_date, visit_type, session) "
- + "VALUES ((SELECT id FROM moz_places WHERE url = 'invalid-uri'), "
+ + "VALUES ((SELECT id FROM moz_places WHERE url_hash = hash('invalid-uri') AND url = 'invalid-uri'), "
+ TIMESTAMP3 + ", " + Ci.nsINavHistoryService.TRANSITION_TYPED + ", 1)"
);
Async.querySpinningly(stmt);
stmt.finalize();
do_check_attribute_count(store.getAllIDs(), 4);
_("Make sure we report records with invalid URIs.");
let invalid_uri_guid = Utils.makeGUID();
--- a/services/sync/tps/extensions/tps/resource/modules/history.jsm
+++ b/services/sync/tps/extensions/tps/resource/modules/history.jsm
@@ -65,17 +65,17 @@ var HistoryEntry = {
*/
get _visitStm() {
let stm = this._db.createStatement(
"SELECT visit_type type, visit_date date " +
"FROM moz_historyvisits " +
"WHERE place_id = (" +
"SELECT id " +
"FROM moz_places " +
- "WHERE url = :url) " +
+ "WHERE url_hash = hash(:url) AND url = :url) " +
"ORDER BY date DESC LIMIT 20");
this.__defineGetter__("_visitStm", () => stm);
return stm;
},
/**
* _getVisits
*
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -771,24 +771,20 @@ function updateBookmark(info, item, newP
if (info.hasOwnProperty("lastModified"))
tuples.set("lastModified", { value: PlacesUtils.toPRTime(info.lastModified) });
if (info.hasOwnProperty("title"))
tuples.set("title", { value: info.title });
yield db.executeTransaction(function* () {
if (info.hasOwnProperty("url")) {
// Ensure a page exists in moz_places for this URL.
- yield db.executeCached(
- `INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid)
- VALUES (:url, :rev_host, 0, :frecency, GENERATE_GUID())
- `, { url: info.url ? info.url.href : null,
- rev_host: PlacesUtils.getReversedHost(info.url),
- frecency: info.url.protocol == "place:" ? 0 : -1 });
+ yield maybeInsertPlace(db, info.url);
+ // Update tuples for the update query.
tuples.set("url", { value: info.url.href
- , fragment: "fk = (SELECT id FROM moz_places WHERE url = :url)" });
+ , fragment: "fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url)" });
}
if (newParent) {
// For simplicity, update the index regardless.
let newIndex = info.hasOwnProperty("index") ? info.index : item.index;
tuples.set("position", { value: newIndex });
if (newParent.guid == item.parentGuid) {
@@ -859,35 +855,32 @@ function insertBookmark(item, parent) {
// If a guid was not provided, generate one, so we won't need to fetch the
// bookmark just after having created it.
if (!item.hasOwnProperty("guid"))
item.guid = (yield db.executeCached("SELECT GENERATE_GUID() AS guid"))[0].getResultByName("guid");
yield db.executeTransaction(function* transaction() {
if (item.type == Bookmarks.TYPE_BOOKMARK) {
// Ensure a page exists in moz_places for this URL.
- yield db.executeCached(
- `INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid)
- VALUES (:url, :rev_host, 0, :frecency, GENERATE_GUID())
- `, { url: item.url.href, rev_host: PlacesUtils.getReversedHost(item.url),
- frecency: item.url.protocol == "place:" ? 0 : -1 });
+ // The IGNORE conflict can trigger on `guid`.
+ yield maybeInsertPlace(db, item.url);
}
// Adjust indices.
yield db.executeCached(
`UPDATE moz_bookmarks SET position = position + 1
WHERE parent = :parent
AND position >= :index
`, { parent: parent._id, index: item.index });
// Insert the bookmark into the database.
yield db.executeCached(
`INSERT INTO moz_bookmarks (fk, type, parent, position, title,
dateAdded, lastModified, guid)
- VALUES ((SELECT id FROM moz_places WHERE url = :url), :type, :parent,
+ VALUES ((SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url), :type, :parent,
:index, :title, :date_added, :last_modified, :guid)
`, { url: item.hasOwnProperty("url") ? item.url.href : "nonexistent",
type: item.type, parent: parent._id, index: item.index,
title: item.title, date_added: PlacesUtils.toPRTime(item.dateAdded),
last_modified: PlacesUtils.toPRTime(item.lastModified), guid: item.guid });
yield setAncestorsLastModified(db, item.parentGuid, item.dateAdded);
});
@@ -1013,17 +1006,17 @@ function fetchBookmarksByURL(info) {
SELECT b.guid, IFNULL(p.guid, "") AS parentGuid, b.position AS 'index',
b.dateAdded, b.lastModified, b.type, b.title, h.url AS url,
b.id AS _id, b.parent AS _parentId,
(SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
p.parent AS _grandParentId
FROM moz_bookmarks b
LEFT JOIN moz_bookmarks p ON p.id = b.parent
LEFT JOIN moz_places h ON h.id = b.fk
- WHERE h.url = :url
+ WHERE h.url_hash = hash(:url) AND h.url = :url
AND _grandParentId <> :tags_folder
ORDER BY b.lastModified DESC
`, { url: info.url.href,
tags_folder: PlacesUtils.tagsFolderId });
return rows.length ? rowsToItemsArray(rows) : null;
}));
}
@@ -1393,27 +1386,28 @@ function validateBookmarkObject(input, b
* Updates frecency for a list of URLs.
*
* @param db
* the Sqlite.jsm connection handle.
* @param urls
* the array of URLs to update.
*/
var updateFrecency = Task.async(function* (db, urls) {
+ // We just use the hashes, since updating a few additional urls won't hurt.
yield db.execute(
`UPDATE moz_places
SET frecency = NOTIFY_FRECENCY(
CALCULATE_FRECENCY(id), url, guid, hidden, last_visit_date
- ) WHERE url IN ( ${urls.map(url => JSON.stringify(url.href)).join(", ")} )
+ ) WHERE url_hash IN ( ${urls.map(url => `hash("${url.href}")`).join(", ")} )
`);
yield db.execute(
`UPDATE moz_places
SET hidden = 0
- WHERE url IN ( ${urls.map(url => JSON.stringify(url.href)).join(", ")} )
+ WHERE url_hash IN ( ${urls.map(url => `hash(${JSON.stringify(url.href)})`).join(", ")} )
AND frecency <> 0
`);
});
/**
* Removes any orphan annotation entries.
*
* @param db
@@ -1559,8 +1553,26 @@ Task.async(function* (db, folderGuids) {
PlacesUtils.toPRTime(entry.lastModified),
entry.type, entry._parentId,
entry.guid, entry.parentGuid,
"" ]);
}
}
}
});
+
+/**
+ * Tries to insert a new place if it doesn't exist yet.
+ * @param url
+ * A valid URL object.
+ * @return {Promise} resolved when the operation is complete.
+ */
+function maybeInsertPlace(db, url) {
+ // The IGNORE conflict can trigger on `guid`.
+ return db.executeCached(
+ `INSERT OR IGNORE INTO moz_places (url, url_hash, rev_host, hidden, frecency, guid)
+ VALUES (:url, hash(:url), :rev_host, 0, :frecency,
+ IFNULL((SELECT guid FROM moz_places WHERE url_hash = hash(:url) AND url = :url),
+ GENERATE_GUID()))
+ `, { url: url.href,
+ rev_host: PlacesUtils.getReversedHost(url),
+ frecency: url.protocol == "place:" ? 0 : -1 });
+}
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -842,31 +842,38 @@ Database::InitSchema(bool* aDatabaseMigr
if (currentSchemaVersion < 32) {
rv = MigrateV32Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 49 uses schema version 32.
+ if (currentSchemaVersion < 33) {
+ rv = MigrateV33Up();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Firefox 50 uses schema version 33.
+
// Schema Upgrades must add migration code here.
rv = UpdateBookmarkRootTitles();
// We don't want a broken localization to cause us to think
// the database is corrupt and needs to be replaced.
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
else {
// This is a new database, so we have to create all the tables and indices.
// moz_places.
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES);
NS_ENSURE_SUCCESS(rv, rv);
- rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL);
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL_HASH);
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FAVICON);
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_REVHOST);
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_VISITCOUNT);
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FRECENCY);
@@ -1034,16 +1041,18 @@ Database::InitFunctions()
rv = GenerateGUIDFunction::create(mMainConn);
NS_ENSURE_SUCCESS(rv, rv);
rv = FixupURLFunction::create(mMainConn);
NS_ENSURE_SUCCESS(rv, rv);
rv = FrecencyNotificationFunction::create(mMainConn);
NS_ENSURE_SUCCESS(rv, rv);
rv = StoreLastInsertedIdFunction::create(mMainConn);
NS_ENSURE_SUCCESS(rv, rv);
+ rv = HashFunction::create(mMainConn);
+ NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::InitTempEntities()
{
MOZ_ASSERT(NS_IsMainThread());
@@ -1766,16 +1775,49 @@ Database::MigrateV32Up() {
nsCOMPtr<mozIStoragePendingStatement> ps;
rv = mMainConn->ExecuteAsync(stmts, ArrayLength(stmts), nullptr,
getter_AddRefs(ps));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
+nsresult
+Database::MigrateV33Up() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "DROP INDEX IF EXISTS moz_places_url_uniqueindex"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Add an url_hash column to moz_places.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT url_hash FROM moz_places"
+ ), getter_AddRefs(stmt));
+ if (NS_FAILED(rv)) {
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_places ADD COLUMN url_hash INTEGER DEFAULT 0 NOT NULL"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "UPDATE moz_places SET url_hash = hash(url) WHERE url_hash = 0"
+ ));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create an index on url_hash.
+ rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL_HASH);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
void
Database::Shutdown()
{
// As the last step in the shutdown path, finalize the database handle.
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mClosed);
// Break cycles with the shutdown blockers.
@@ -1797,43 +1839,67 @@ Database::Shutdown()
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT 1 "
"FROM moz_places "
"WHERE guid IS NULL "
), getter_AddRefs(stmt));
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = stmt->ExecuteStep(&haveNullGuids);
MOZ_ASSERT(NS_SUCCEEDED(rv));
- MOZ_ASSERT(!haveNullGuids && "Found a page without a GUID!");
+ MOZ_ASSERT(!haveNullGuids, "Found a page without a GUID!");
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT 1 "
"FROM moz_bookmarks "
"WHERE guid IS NULL "
), getter_AddRefs(stmt));
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = stmt->ExecuteStep(&haveNullGuids);
MOZ_ASSERT(NS_SUCCEEDED(rv));
- MOZ_ASSERT(!haveNullGuids && "Found a bookmark without a GUID!");
+ MOZ_ASSERT(!haveNullGuids, "Found a bookmark without a GUID!");
}
{ // Sanity check for unrounded dateAdded and lastModified values (bug
// 1107308).
bool hasUnroundedDates = false;
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT 1 "
"FROM moz_bookmarks "
"WHERE dateAdded % 1000 > 0 OR lastModified % 1000 > 0 LIMIT 1"
), getter_AddRefs(stmt));
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = stmt->ExecuteStep(&hasUnroundedDates);
MOZ_ASSERT(NS_SUCCEEDED(rv));
- MOZ_ASSERT(!hasUnroundedDates && "Found unrounded dates!");
+ MOZ_ASSERT(!hasUnroundedDates, "Found unrounded dates!");
+ }
+
+ { // Sanity check url_hash
+ bool hasNullHash = false;
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT 1 FROM moz_places WHERE url_hash = 0"
+ ), getter_AddRefs(stmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = stmt->ExecuteStep(&hasNullHash);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(!hasNullHash, "Found a place without a hash!");
+ }
+
+ { // Sanity check unique urls
+ bool hasDupeUrls = false;
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT 1 FROM moz_places GROUP BY url HAVING count(*) > 1 "
+ ), getter_AddRefs(stmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = stmt->ExecuteStep(&hasDupeUrls);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(!hasDupeUrls, "Found a duplicate url!");
}
#endif
mMainThreadStatements.FinalizeStatements();
mMainThreadAsyncStatements.FinalizeStatements();
RefPtr< FinalizeStatementCacheProxy<mozIStorageStatement> > event =
new FinalizeStatementCacheProxy<mozIStorageStatement>(
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -13,17 +13,17 @@
#include "mozilla/storage.h"
#include "mozilla/storage/StatementCache.h"
#include "mozilla/Attributes.h"
#include "nsIEventTarget.h"
#include "Shutdown.h"
// This is the schema version. Update it at any schema change and add a
// corresponding migrateVxx method below.
-#define DATABASE_SCHEMA_VERSION 32
+#define DATABASE_SCHEMA_VERSION 33
// Fired after Places inited.
#define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
// Fired when initialization fails due to a locked database.
#define TOPIC_DATABASE_LOCKED "places-database-locked"
// This topic is received when the profile is about to be lost. Places does
// initial shutdown work and notifies TOPIC_PLACES_SHUTDOWN to all listeners.
// Any shutdown work that requires the Places APIs should happen here.
@@ -263,16 +263,17 @@ protected:
nsresult MigrateV24Up();
nsresult MigrateV25Up();
nsresult MigrateV26Up();
nsresult MigrateV27Up();
nsresult MigrateV28Up();
nsresult MigrateV30Up();
nsresult MigrateV31Up();
nsresult MigrateV32Up();
+ nsresult MigrateV33Up();
nsresult UpdateBookmarkRootTitles();
friend class ConnectionShutdownBlocker;
private:
~Database();
--- a/toolkit/components/places/FaviconHelpers.cpp
+++ b/toolkit/components/places/FaviconHelpers.cpp
@@ -58,17 +58,17 @@ FetchPageInfo(const RefPtr<Database>& aD
"LEFT JOIN moz_historyvisits parent ON parent.id = dest.from_visit "
"AND dest.visit_type IN (%d, %d) "
"LEFT JOIN moz_historyvisits grandparent ON parent.from_visit = grandparent.id "
"AND parent.visit_type IN (%d, %d) "
"WHERE dest.place_id = h.id "
"AND EXISTS(SELECT 1 FROM moz_bookmarks b WHERE b.fk = r_place_id) "
"LIMIT 1 "
") "
- ") FROM moz_places h WHERE h.url = :page_url",
+ ") FROM moz_places h WHERE h.url_hash = hash(:page_url) AND h.url = :page_url",
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY,
nsINavHistoryService::TRANSITION_REDIRECT_PERMANENT,
nsINavHistoryService::TRANSITION_REDIRECT_TEMPORARY
);
nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(query);
NS_ENSURE_STATE(stmt);
@@ -243,17 +243,17 @@ FetchIconURL(const RefPtr<Database>& aDB
MOZ_ASSERT(!NS_IsMainThread());
aIconSpec.Truncate();
nsCOMPtr<mozIStorageStatement> stmt = aDB->GetStatement(
"SELECT f.url "
"FROM moz_places h "
"JOIN moz_favicons f ON h.favicon_id = f.id "
- "WHERE h.url = :page_url"
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url"
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
aPageSpec);
NS_ENSURE_SUCCESS(rv, rv);
@@ -691,17 +691,18 @@ AsyncAssociateIconToPage::Run()
"UPDATE moz_places SET favicon_id = :icon_id WHERE id = :page_id"
);
NS_ENSURE_STATE(stmt);
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("page_id"), mPage.id);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
stmt = DB->GetStatement(
- "UPDATE moz_places SET favicon_id = :icon_id WHERE url = :page_url"
+ "UPDATE moz_places SET favicon_id = :icon_id "
+ "WHERE url_hash = hash(:page_url) AND url = :page_url"
);
NS_ENSURE_STATE(stmt);
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), mPage.spec);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("icon_id"), mIcon.id);
NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -1952,17 +1952,17 @@ public:
return NS_OK;
mReadOnlyDBConn = do_QueryInterface(aConnection);
// Now we can create our cached statements.
if (!mIsVisitedStatement) {
(void)mReadOnlyDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
"SELECT 1 FROM moz_places h "
- "WHERE url = ?1 AND last_visit_date NOTNULL "
+ "WHERE url_hash = hash(?1) AND url = ?1 AND last_visit_date NOTNULL "
), getter_AddRefs(mIsVisitedStatement));
MOZ_ASSERT(mIsVisitedStatement);
nsresult result = mIsVisitedStatement ? NS_OK : NS_ERROR_NOT_AVAILABLE;
for (int32_t i = 0; i < mIsVisitedCallbacks.Count(); ++i) {
DebugOnly<nsresult> rv;
rv = mIsVisitedCallbacks[i]->Complete(result, mIsVisitedStatement);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
@@ -2032,18 +2032,18 @@ nsresult
History::InsertPlace(VisitData& aPlace)
{
MOZ_ASSERT(aPlace.placeId == 0, "should not have a valid place id!");
MOZ_ASSERT(!aPlace.shouldUpdateHidden, "We should not need to update hidden");
MOZ_ASSERT(!NS_IsMainThread(), "must be called off of the main thread!");
nsCOMPtr<mozIStorageStatement> stmt = GetStatement(
"INSERT INTO moz_places "
- "(url, title, rev_host, hidden, typed, frecency, guid) "
- "VALUES (:url, :title, :rev_host, :hidden, :typed, :frecency, :guid) "
+ "(url, url_hash, title, rev_host, hidden, typed, frecency, guid) "
+ "VALUES (:url, hash(:url), :title, :rev_host, :hidden, :typed, :frecency, :guid) "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("rev_host"),
aPlace.revHost);
NS_ENSURE_SUCCESS(rv, rv);
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("url"), aPlace.spec);
@@ -2142,17 +2142,17 @@ History::FetchPageInfo(VisitData& _place
nsCOMPtr<mozIStorageStatement> stmt;
bool selectByURI = !_place.spec.IsEmpty();
if (selectByURI) {
stmt = GetStatement(
"SELECT guid, id, title, hidden, typed, frecency, visit_count, last_visit_date, "
"(SELECT id FROM moz_historyvisits "
"WHERE place_id = h.id AND visit_date = h.last_visit_date) AS last_visit_id "
"FROM moz_places h "
- "WHERE url = :page_url "
+ "WHERE url_hash = hash(:page_url) AND url = :page_url "
);
NS_ENSURE_STATE(stmt);
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), _place.spec);
NS_ENSURE_SUCCESS(rv, rv);
}
else {
stmt = GetStatement(
--- a/toolkit/components/places/History.jsm
+++ b/toolkit/components/places/History.jsm
@@ -633,17 +633,18 @@ var clear = Task.async(function* (db) {
let observers = PlacesUtils.history.getObservers();
notify(observers, "onClearHistory");
// Invalidate frecencies for the remaining places. This must happen
// after the notification to ensure it runs enqueued to expiration.
yield db.execute(
`UPDATE moz_places SET frecency =
(CASE
- WHEN url BETWEEN 'place:' AND 'place;'
+ WHEN url_hash BETWEEN hash("place", "prefix_lo") AND
+ hash("place", "prefix_hi")
THEN 0
ELSE -1
END)
WHERE frecency > 0`);
// Notify frecency change observers.
notify(observers, "onManyFrecenciesChanged");
});
@@ -857,20 +858,22 @@ var removeVisitsByFilter = Task.async(fu
return visitsToRemove.length != 0;
});
// Inner implementation of History.remove.
var remove = Task.async(function*(db, {guids, urls}, onResult = null) {
// 1. Find out what needs to be removed
let query =
- `SELECT id, url, guid, foreign_count, title, frecency FROM moz_places
+ `SELECT id, url, guid, foreign_count, title, frecency
+ FROM moz_places
WHERE guid IN (${ sqlList(guids) })
- OR url IN (${ sqlList(urls) })
- `;
+ OR (url_hash IN (${ urls.map(u => "hash(" + JSON.stringify(u) + ")").join(",") })
+ AND url IN (${ sqlList(urls) }))
+ `;
let onResultData = onResult ? [] : null;
let pages = [];
let hasPagesToKeep = false;
let hasPagesToRemove = false;
yield db.execute(query, null, Task.async(function*(row) {
let hasForeign = row.getResultByName("foreign_count") != 0;
if (hasForeign) {
--- a/toolkit/components/places/PlacesDBUtils.jsm
+++ b/toolkit/components/places/PlacesDBUtils.jsm
@@ -713,19 +713,25 @@ this.PlacesDBUtils = {
LEFT JOIN moz_bookmarks on fk = h.id AND fk ISNULL
GROUP BY src.place_id HAVING count(*) = visit_count
)`);
cleanupStatements.push(fixRedirectsHidden);
// L.4 recalculate foreign_count.
let fixForeignCount = DBConn.createAsyncStatement(
`UPDATE moz_places SET foreign_count =
- (SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id )`);
+ (SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id ) +
+ (SELECT count(*) FROM moz_keywords WHERE place_id = moz_places.id )`);
cleanupStatements.push(fixForeignCount);
+ // L.5 recalculate missing hashes.
+ let fixMissingHashes = DBConn.createAsyncStatement(
+ `UPDATE moz_places SET url_hash = hash(url) WHERE url_hash = 0`);
+ cleanupStatements.push(fixMissingHashes);
+
// MAINTENANCE STATEMENTS SHOULD GO ABOVE THIS POINT!
return cleanupStatements;
},
/**
* Tries to vacuum the database.
*
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -1299,17 +1299,17 @@ this.PlacesUtils = {
asyncGetBookmarkIds: function PU_asyncGetBookmarkIds(aURI, aCallback)
{
let abort = false;
let itemIds = [];
Task.spawn(function* () {
let conn = yield this.promiseDBConnection();
const QUERY_STR = `SELECT b.id FROM moz_bookmarks b
JOIN moz_places h on h.id = b.fk
- WHERE h.url = :url`;
+ WHERE h.url_hash = hash(:url) AND h.url = :url`;
let spec = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
yield conn.executeCached(QUERY_STR, { url: spec }, aRow => {
if (abort)
throw StopIteration;
itemIds.push(aRow.getResultByIndex(0));
});
if (!abort)
aCallback(itemIds, aURI);
@@ -2056,32 +2056,34 @@ var Keywords = {
// A keyword can only be associated to a single page.
// If another page is using the new keyword, we must update the keyword
// entry.
// Note we cannot use INSERT OR REPLACE cause it wouldn't invoke the delete
// trigger.
if (oldEntry) {
yield db.executeCached(
`UPDATE moz_keywords
- SET place_id = (SELECT id FROM moz_places WHERE url = :url),
+ SET place_id = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url),
post_data = :post_data
WHERE keyword = :keyword
`, { url: url.href, keyword: keyword, post_data: postData });
yield notifyKeywordChange(oldEntry.url.href, "");
} else {
// An entry for the given page could be missing, in such a case we need to
- // create it.
+ // create it. The IGNORE conflict can trigger on `guid`.
yield db.executeCached(
- `INSERT OR IGNORE INTO moz_places (url, rev_host, hidden, frecency, guid)
- VALUES (:url, :rev_host, 0, :frecency, GENERATE_GUID())
+ `INSERT OR IGNORE INTO moz_places (url, url_hash, rev_host, hidden, frecency, guid)
+ VALUES (:url, hash(:url), :rev_host, 0, :frecency,
+ IFNULL((SELECT guid FROM moz_places WHERE url_hash = hash(:url) AND url = :url),
+ GENERATE_GUID()))
`, { url: url.href, rev_host: PlacesUtils.getReversedHost(url),
frecency: url.protocol == "place:" ? 0 : -1 });
yield db.executeCached(
`INSERT INTO moz_keywords (keyword, place_id, post_data)
- VALUES (:keyword, (SELECT id FROM moz_places WHERE url = :url), :post_data)
+ VALUES (:keyword, (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url), :post_data)
`, { url: url.href, keyword: keyword, post_data: postData });
}
cache.set(keyword, { keyword, url, postData });
// In any case, notify about the new keyword.
yield notifyKeywordChange(url.href, keyword);
}.bind(this))
--- a/toolkit/components/places/SQLFunctions.cpp
+++ b/toolkit/components/places/SQLFunctions.cpp
@@ -12,16 +12,17 @@
#include "SQLFunctions.h"
#include "nsMathUtils.h"
#include "nsUTF8Utils.h"
#include "nsINavHistoryService.h"
#include "nsPrintfCString.h"
#include "nsNavHistory.h"
#include "mozilla/Likely.h"
#include "nsVariant.h"
+#include "mozilla/HashFunctions.h"
// Maximum number of chars to search through.
// MatchAutoCompleteFunction won't look for matches over this threshold.
#define MAX_CHARS_TO_SEARCH_THROUGH 255
using namespace mozilla::storage;
// Keep the GUID-related parts of this file in sync with toolkit/downloads/SQLFunctions.cpp!
@@ -178,23 +179,16 @@ namespace {
} // End anonymous namespace
namespace mozilla {
namespace places {
////////////////////////////////////////////////////////////////////////////////
//// AutoComplete Matching Function
- //////////////////////////////////////////////////////////////////////////////
- //// MatchAutoCompleteFunction
-
- MatchAutoCompleteFunction::~MatchAutoCompleteFunction()
- {
- }
-
/* static */
nsresult
MatchAutoCompleteFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<MatchAutoCompleteFunction> function =
new MatchAutoCompleteFunction();
nsresult rv = aDBConn->CreateFunction(
@@ -323,19 +317,16 @@ namespace places {
};
}
NS_IMPL_ISUPPORTS(
MatchAutoCompleteFunction,
mozIStorageFunction
)
- //////////////////////////////////////////////////////////////////////////////
- //// mozIStorageFunction
-
NS_IMETHODIMP
MatchAutoCompleteFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
nsIVariant **_result)
{
// Macro to make the code a bit cleaner and easier to read. Operates on
// searchBehavior.
int32_t searchBehavior = aArguments->AsInt32(kArgIndexSearchBehavior);
#define HAS_BEHAVIOR(aBitName) \
@@ -433,23 +424,16 @@ namespace places {
return NS_OK;
#undef HAS_BEHAVIOR
}
////////////////////////////////////////////////////////////////////////////////
//// Frecency Calculation Function
- //////////////////////////////////////////////////////////////////////////////
- //// CalculateFrecencyFunction
-
- CalculateFrecencyFunction::~CalculateFrecencyFunction()
- {
- }
-
/* static */
nsresult
CalculateFrecencyFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<CalculateFrecencyFunction> function =
new CalculateFrecencyFunction();
nsresult rv = aDBConn->CreateFunction(
@@ -460,19 +444,16 @@ namespace places {
return NS_OK;
}
NS_IMPL_ISUPPORTS(
CalculateFrecencyFunction,
mozIStorageFunction
)
- //////////////////////////////////////////////////////////////////////////////
- //// mozIStorageFunction
-
NS_IMETHODIMP
CalculateFrecencyFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
nsIVariant **_result)
{
// Fetch arguments. Use default values if they were omitted.
uint32_t numEntries;
nsresult rv = aArguments->GetNumEntries(&numEntries);
NS_ENSURE_SUCCESS(rv, rv);
@@ -623,23 +604,16 @@ namespace places {
NS_ADDREF(*_result = new IntegerVariant((int32_t) ceilf(visitCount * ceilf(pointsForSampledVisits))));
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// GUID Creation Function
- GenerateGUIDFunction::~GenerateGUIDFunction()
- {
- }
-
- //////////////////////////////////////////////////////////////////////////////
- //// GenerateGUIDFunction
-
/* static */
nsresult
GenerateGUIDFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<GenerateGUIDFunction> function = new GenerateGUIDFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("generate_guid"), 0, function
);
@@ -648,41 +622,31 @@ namespace places {
return NS_OK;
}
NS_IMPL_ISUPPORTS(
GenerateGUIDFunction,
mozIStorageFunction
)
- //////////////////////////////////////////////////////////////////////////////
- //// mozIStorageFunction
-
NS_IMETHODIMP
GenerateGUIDFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
nsIVariant **_result)
{
nsAutoCString guid;
nsresult rv = GenerateGUID(guid);
NS_ENSURE_SUCCESS(rv, rv);
NS_ADDREF(*_result = new UTF8TextVariant(guid));
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Get Unreversed Host Function
- GetUnreversedHostFunction::~GetUnreversedHostFunction()
- {
- }
-
- //////////////////////////////////////////////////////////////////////////////
- //// GetUnreversedHostFunction
-
/* static */
nsresult
GetUnreversedHostFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<GetUnreversedHostFunction> function = new GetUnreversedHostFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("get_unreversed_host"), 1, function
);
@@ -691,19 +655,16 @@ namespace places {
return NS_OK;
}
NS_IMPL_ISUPPORTS(
GetUnreversedHostFunction,
mozIStorageFunction
)
- //////////////////////////////////////////////////////////////////////////////
- //// mozIStorageFunction
-
NS_IMETHODIMP
GetUnreversedHostFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
nsIVariant **_result)
{
// Must have non-null function arguments.
MOZ_ASSERT(aArguments);
nsAutoString src;
@@ -722,23 +683,16 @@ namespace places {
}
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Fixup URL Function
- FixupURLFunction::~FixupURLFunction()
- {
- }
-
- //////////////////////////////////////////////////////////////////////////////
- //// FixupURLFunction
-
/* static */
nsresult
FixupURLFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<FixupURLFunction> function = new FixupURLFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("fixup_url"), 1, function
);
@@ -747,19 +701,16 @@ namespace places {
return NS_OK;
}
NS_IMPL_ISUPPORTS(
FixupURLFunction,
mozIStorageFunction
)
- //////////////////////////////////////////////////////////////////////////////
- //// mozIStorageFunction
-
NS_IMETHODIMP
FixupURLFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
nsIVariant **_result)
{
// Must have non-null function arguments.
MOZ_ASSERT(aArguments);
nsAutoString src;
@@ -782,20 +733,16 @@ namespace places {
result->SetAsAString(src);
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Frecency Changed Notification Function
- FrecencyNotificationFunction::~FrecencyNotificationFunction()
- {
- }
-
/* static */
nsresult
FrecencyNotificationFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<FrecencyNotificationFunction> function =
new FrecencyNotificationFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("notify_frecency"), 5, function
@@ -842,20 +789,16 @@ namespace places {
NS_ENSURE_SUCCESS(rv, rv);
result.forget(_result);
return NS_OK;
}
////////////////////////////////////////////////////////////////////////////////
//// Store Last Inserted Id Function
- StoreLastInsertedIdFunction::~StoreLastInsertedIdFunction()
- {
- }
-
/* static */
nsresult
StoreLastInsertedIdFunction::create(mozIStorageConnection *aDBConn)
{
RefPtr<StoreLastInsertedIdFunction> function =
new StoreLastInsertedIdFunction();
nsresult rv = aDBConn->CreateFunction(
NS_LITERAL_CSTRING("store_last_inserted_id"), 2, function
@@ -889,10 +832,88 @@ namespace places {
RefPtr<nsVariant> result = new nsVariant();
rv = result->SetAsInt64(lastInsertedId);
NS_ENSURE_SUCCESS(rv, rv);
result.forget(_result);
return NS_OK;
}
+////////////////////////////////////////////////////////////////////////////////
+//// Hash Function
+
+ /* static */
+ nsresult
+ HashFunction::create(mozIStorageConnection *aDBConn)
+ {
+ RefPtr<HashFunction> function = new HashFunction();
+ return aDBConn->CreateFunction(
+ NS_LITERAL_CSTRING("hash"), -1, function
+ );
+ }
+
+ NS_IMPL_ISUPPORTS(
+ HashFunction,
+ mozIStorageFunction
+ )
+
+ NS_IMETHODIMP
+ HashFunction::OnFunctionCall(mozIStorageValueArray *aArguments,
+ nsIVariant **_result)
+ {
+ // Must have non-null function arguments.
+ MOZ_ASSERT(aArguments);
+
+ // Fetch arguments. Use default values if they were omitted.
+ uint32_t numEntries;
+ nsresult rv = aArguments->GetNumEntries(&numEntries);
+ NS_ENSURE_SUCCESS(rv, rv);
+ NS_ENSURE_TRUE(numEntries >= 1 && numEntries <= 2, NS_ERROR_FAILURE);
+
+ nsString str;
+ aArguments->GetString(0, str);
+ nsAutoCString mode;
+ if (numEntries > 1) {
+ aArguments->GetUTF8String(1, mode);
+ }
+
+ RefPtr<nsVariant> result = new nsVariant();
+ if (mode.IsEmpty()) {
+ // URI-like strings (having a prefix before a colon), are handled specially,
+ // as a 48 bit hash, where first 16 bits are the prefix hash, while the
+ // other 48 are the string hash.
+ // The 16 bits have been decided based on the fact hashing all of the IANA
+ // known schemes, plus "places", does not generate collisions.
+ nsAString::const_iterator start, tip, end;
+ str.BeginReading(tip);
+ start = tip;
+ str.EndReading(end);
+ if (FindInReadable(NS_LITERAL_STRING(":"), tip, end)) {
+ const nsDependentSubstring& prefix = Substring(start, tip);
+ uint64_t prefixHash = static_cast<uint64_t>(HashString(prefix) & 0x0000FFFF);
+ // The second half of the url is more likely to be unique, so we add it.
+ uint32_t srcHash = HashString(str);
+ uint64_t hash = (prefixHash << 32) + srcHash;
+ result->SetAsInt64(hash);
+ } else {
+ uint32_t hash = HashString(str);
+ result->SetAsInt64(hash);
+ }
+ } else if (mode.Equals(NS_LITERAL_CSTRING("prefix_lo"))) {
+ // Keep only 16 bits.
+ uint64_t hash = static_cast<uint64_t>(HashString(str) & 0x0000FFFF) << 32;
+ result->SetAsInt64(hash);
+ } else if (mode.Equals(NS_LITERAL_CSTRING("prefix_hi"))) {
+ // Keep only 16 bits.
+ uint64_t hash = static_cast<uint64_t>(HashString(str) & 0x0000FFFF) << 32;
+ // Make this a prefix upper bound by filling the lowest 32 bits.
+ hash += 0xFFFFFFFF;
+ result->SetAsInt64(hash);
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ result.forget(_result);
+ return NS_OK;
+ }
+
} // namespace places
} // namespace mozilla
--- a/toolkit/components/places/SQLFunctions.h
+++ b/toolkit/components/places/SQLFunctions.h
@@ -66,17 +66,17 @@ public:
* Registers the function with the specified database connection.
*
* @param aDBConn
* The database connection to register with.
*/
static nsresult create(mozIStorageConnection *aDBConn);
private:
- ~MatchAutoCompleteFunction();
+ ~MatchAutoCompleteFunction() {}
/**
* Argument Indexes
*/
static const uint32_t kArgSearchString = 0;
static const uint32_t kArgIndexURL = 1;
static const uint32_t kArgIndexTitle = 2;
static const uint32_t kArgIndexTags = 3;
@@ -169,17 +169,16 @@ private:
* @param _fixedSpec
* An out parameter that is the fixed up string.
*/
static void fixupURISpec(const nsCString &aURISpec, int32_t aMatchBehavior,
nsCString &_fixedSpec);
};
-
////////////////////////////////////////////////////////////////////////////////
//// Frecency Calculation Function
/**
* This function is used to calculate frecency for a page.
*
* In SQL, you'd use it in when setting frecency like:
* SET frecency = CALCULATE_FRECENCY(place_id).
@@ -192,74 +191,77 @@ private:
* Whether the page has been typed in. Default is false.
* @param [optional] fullVisitCount
* Count of all the visits (All types). Default is 0.
* @param [optional] isBookmarked
* Whether the page is bookmarked. Default is false.
*/
class CalculateFrecencyFunction final : public mozIStorageFunction
{
- ~CalculateFrecencyFunction();
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
/**
* Registers the function with the specified database connection.
*
* @param aDBConn
* The database connection to register with.
*/
static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~CalculateFrecencyFunction() {}
};
/**
* SQL function to generate a GUID for a place or bookmark item. This is just
* a wrapper around GenerateGUID in Helpers.h.
*
* @return a guid for the item.
*/
class GenerateGUIDFunction final : public mozIStorageFunction
{
- ~GenerateGUIDFunction();
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
/**
* Registers the function with the specified database connection.
*
* @param aDBConn
* The database connection to register with.
*/
static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~GenerateGUIDFunction() {}
};
/**
* SQL function to unreverse the rev_host of a page.
*
* @param rev_host
* The rev_host value of the page.
*
* @return the unreversed host of the page.
*/
class GetUnreversedHostFunction final : public mozIStorageFunction
{
- ~GetUnreversedHostFunction();
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
/**
* Registers the function with the specified database connection.
*
* @param aDBConn
* The database connection to register with.
*/
static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~GetUnreversedHostFunction() {}
};
////////////////////////////////////////////////////////////////////////////////
//// Fixup URL Function
/**
* Make a given URL more suitable for searches, by removing common prefixes
@@ -267,28 +269,29 @@ public:
*
* @param url
* A URL.
* @return
* The same URL, with redundant parts removed.
*/
class FixupURLFunction final : public mozIStorageFunction
{
- ~FixupURLFunction();
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
/**
* Registers the function with the specified database connection.
*
* @param aDBConn
* The database connection to register with.
*/
static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~FixupURLFunction() {}
};
////////////////////////////////////////////////////////////////////////////////
//// Frecency Changed Notification Function
/**
* For a given place, posts a runnable to the main thread that calls
@@ -304,28 +307,29 @@ public:
* @param hidden
* The place's hidden boolean.
* @param lastVisitDate
* The place's last visit date.
* @return newFrecency
*/
class FrecencyNotificationFunction final : public mozIStorageFunction
{
- ~FrecencyNotificationFunction();
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
/**
* Registers the function with the specified database connection.
*
* @param aDBConn
* The database connection to register with.
*/
static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~FrecencyNotificationFunction() {}
};
////////////////////////////////////////////////////////////////////////////////
//// Store Last Inserted Id Function
/**
* Store the last inserted id for reference purpose.
@@ -333,26 +337,56 @@ public:
* @param tableName
* The table name.
* @param id
* The last inserted id.
* @return null
*/
class StoreLastInsertedIdFunction final : public mozIStorageFunction
{
- ~StoreLastInsertedIdFunction();
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_MOZISTORAGEFUNCTION
/**
* Registers the function with the specified database connection.
*
* @param aDBConn
* The database connection to register with.
*/
static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~StoreLastInsertedIdFunction() {}
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Hash Function
+
+/**
+ * Calculates hash for a given string using the mfbt AddToHash function.
+ *
+ * @param string
+ * A string.
+ * @return
+ * The hash for the string.
+ */
+class HashFunction final : public mozIStorageFunction
+{
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+
+ /**
+ * Registers the function with the specified database connection.
+ *
+ * @param aDBConn
+ * The database connection to register with.
+ */
+ static nsresult create(mozIStorageConnection *aDBConn);
+private:
+ ~HashFunction() {}
};
} // namespace places
} // namespace mozilla
#endif // mozilla_places_SQLFunctions_h_
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -127,17 +127,17 @@ function defaultQuery(conditions = "") {
LIMIT :maxResults`;
return query;
}
const SQL_SWITCHTAB_QUERY =
`SELECT :query_type, t.url, t.url, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
t.open_count, NULL
FROM moz_openpages_temp t
- LEFT JOIN moz_places h ON h.url = t.url
+ LEFT JOIN moz_places h ON h.url_hash = hash(t.url) AND h.url = t.url
WHERE h.id IS NULL
AND AUTOCOMPLETE_MATCH(:searchString, t.url, t.url, NULL,
NULL, NULL, NULL, t.open_count,
:matchBehavior, :searchBehavior)
ORDER BY t.ROWID DESC
LIMIT :maxResults`;
const SQL_ADAPTIVE_QUERY =
--- a/toolkit/components/places/nsAnnotationService.cpp
+++ b/toolkit/components/places/nsAnnotationService.cpp
@@ -1255,17 +1255,17 @@ nsAnnotationService::GetAnnotationNamesT
);
}
else {
statement = mDB->GetStatement(
"SELECT n.name "
"FROM moz_anno_attributes n "
"JOIN moz_annos a ON a.anno_attribute_id = n.id "
"JOIN moz_places h ON h.id = a.place_id "
- "WHERE h.url = :page_url"
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url"
);
}
NS_ENSURE_STATE(statement);
mozStorageStatementScoper scoper(statement);
nsresult rv;
if (isItemAnnotation)
rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), aItemId);
@@ -1377,17 +1377,18 @@ nsAnnotationService::RemoveAnnotationInt
"WHERE item_id = :item_id "
"AND anno_attribute_id = "
"(SELECT id FROM moz_anno_attributes WHERE name = :anno_name)"
);
}
else {
statement = mDB->GetStatement(
"DELETE FROM moz_annos "
- "WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url) "
+ "WHERE place_id = "
+ "(SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
"AND anno_attribute_id = "
"(SELECT id FROM moz_anno_attributes WHERE name = :anno_name)"
);
}
NS_ENSURE_STATE(statement);
mozStorageStatementScoper scoper(statement);
nsresult rv;
@@ -1440,17 +1441,17 @@ nsAnnotationService::RemoveItemAnnotatio
NS_IMETHODIMP
nsAnnotationService::RemovePageAnnotations(nsIURI* aURI)
{
NS_ENSURE_ARG(aURI);
// Should this be precompiled or a getter?
nsCOMPtr<mozIStorageStatement> statement = mDB->GetStatement(
"DELETE FROM moz_annos WHERE place_id = "
- "(SELECT id FROM moz_places WHERE url = :page_url)"
+ "(SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url)"
);
NS_ENSURE_STATE(statement);
mozStorageStatementScoper scoper(statement);
nsresult rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
rv = statement->Execute();
@@ -1503,33 +1504,33 @@ nsAnnotationService::CopyPageAnnotations
mozStorageTransaction transaction(mDB->MainConn(), false);
nsCOMPtr<mozIStorageStatement> sourceStmt = mDB->GetStatement(
"SELECT h.id, n.id, n.name, a2.id "
"FROM moz_places h "
"JOIN moz_annos a ON a.place_id = h.id "
"JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id "
"LEFT JOIN moz_annos a2 ON a2.place_id = "
- "(SELECT id FROM moz_places WHERE url = :dest_url) "
+ "(SELECT id FROM moz_places WHERE url_hash = hash(:dest_url) AND url = :dest_url) "
"AND a2.anno_attribute_id = n.id "
"WHERE url = :source_url"
);
NS_ENSURE_STATE(sourceStmt);
mozStorageStatementScoper sourceScoper(sourceStmt);
nsresult rv = URIBinder::Bind(sourceStmt, NS_LITERAL_CSTRING("source_url"), aSourceURI);
NS_ENSURE_SUCCESS(rv, rv);
rv = URIBinder::Bind(sourceStmt, NS_LITERAL_CSTRING("dest_url"), aDestURI);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageStatement> copyStmt = mDB->GetStatement(
"INSERT INTO moz_annos "
"(place_id, anno_attribute_id, content, flags, expiration, "
"type, dateAdded, lastModified) "
- "SELECT (SELECT id FROM moz_places WHERE url = :page_url), "
+ "SELECT (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url), "
"anno_attribute_id, content, flags, expiration, type, "
":date, :date "
"FROM moz_annos "
"WHERE place_id = :page_id "
"AND anno_attribute_id = :name_id"
);
NS_ENSURE_STATE(copyStmt);
mozStorageStatementScoper copyScoper(copyStmt);
@@ -1698,17 +1699,17 @@ nsAnnotationService::HasAnnotationIntern
else {
stmt = mDB->GetStatement(
"SELECT h.id, "
"(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS nameid, "
"a.id, a.dateAdded "
"FROM moz_places h "
"LEFT JOIN moz_annos a ON a.place_id = h.id "
"AND a.anno_attribute_id = nameid "
- "WHERE h.url = :page_url"
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url"
);
}
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper checkAnnoScoper(stmt);
nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName);
NS_ENSURE_SUCCESS(rv, rv);
if (isItemAnnotation)
@@ -1765,17 +1766,17 @@ nsAnnotationService::StartGetAnnotation(
}
else {
aStatement = mDB->GetStatement(
"SELECT a.id, a.place_id, :anno_name, a.content, a.flags, "
"a.expiration, a.type "
"FROM moz_anno_attributes n "
"JOIN moz_annos a ON n.id = a.anno_attribute_id "
"JOIN moz_places h ON h.id = a.place_id "
- "WHERE h.url = :page_url "
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url "
"AND n.name = :anno_name"
);
}
NS_ENSURE_STATE(aStatement);
mozStorageStatementScoper getAnnoScoper(aStatement);
nsresult rv;
if (isItemAnnotation)
@@ -1859,17 +1860,17 @@ nsAnnotationService::StartSetAnnotation(
else {
stmt = mDB->GetStatement(
"SELECT h.id, "
"(SELECT id FROM moz_anno_attributes WHERE name = :anno_name) AS nameid, "
"a.id, a.dateAdded "
"FROM moz_places h "
"LEFT JOIN moz_annos a ON a.place_id = h.id "
"AND a.anno_attribute_id = nameid "
- "WHERE h.url = :page_url"
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url"
);
}
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper checkAnnoScoper(stmt);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), aName);
NS_ENSURE_SUCCESS(rv, rv);
if (isItemAnnotation)
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -55,17 +55,17 @@ public:
{
RefPtr<Database> DB = Database::GetDatabase();
if (DB) {
nsCOMPtr<mozIStorageAsyncStatement> stmt = DB->GetAsyncStatement(
"/* do not warn (bug 1175249) */ "
"SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
"FROM moz_bookmarks b "
"JOIN moz_bookmarks t on t.id = b.parent "
- "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
+ "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
"ORDER BY b.lastModified DESC, b.id DESC "
);
if (stmt) {
(void)URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"),
mData.bookmark.url);
nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
(void)stmt->ExecuteAsync(this, getter_AddRefs(pendingStmt));
}
@@ -1848,17 +1848,17 @@ NS_IMETHODIMP
nsNavBookmarks::IsBookmarked(nsIURI* aURI, bool* aBookmarked)
{
NS_ENSURE_ARG(aURI);
NS_ENSURE_ARG_POINTER(aBookmarked);
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT 1 FROM moz_bookmarks b "
"JOIN moz_places h ON b.fk = h.id "
- "WHERE h.url = :page_url"
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url"
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->ExecuteStep(aBookmarked);
NS_ENSURE_SUCCESS(rv, rv);
@@ -2054,17 +2054,17 @@ nsNavBookmarks::GetBookmarkIdsForURITArr
// Double ordering covers possible lastModified ties, that could happen when
// importing, syncing or due to extensions.
// Note: not using a JOIN is cheaper in this case.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"/* do not warn (bug 1175249) */ "
"SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
"FROM moz_bookmarks b "
"JOIN moz_bookmarks t on t.id = b.parent "
- "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
+ "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
"ORDER BY b.lastModified DESC, b.id DESC "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
@@ -2098,17 +2098,17 @@ nsNavBookmarks::GetBookmarksForURI(nsIUR
// Double ordering covers possible lastModified ties, that could happen when
// importing, syncing or due to extensions.
// Note: not using a JOIN is cheaper in this case.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"/* do not warn (bug 1175249) */ "
"SELECT b.id, b.guid, b.parent, b.lastModified, t.guid, t.parent "
"FROM moz_bookmarks b "
"JOIN moz_bookmarks t on t.id = b.parent "
- "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
+ "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
"ORDER BY b.lastModified DESC, b.id DESC "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -364,17 +364,17 @@ nsNavHistory::GetIdForPage(nsIURI* aURI,
int64_t* _pageId,
nsCString& _GUID)
{
*_pageId = 0;
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT id, url, title, rev_host, visit_count, guid "
"FROM moz_places "
- "WHERE url = :page_url "
+ "WHERE url_hash = hash(:page_url) AND url = :page_url "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
bool hasEntry = false;
@@ -400,18 +400,18 @@ nsNavHistory::GetOrCreateIdForPage(nsIUR
NS_ENSURE_SUCCESS(rv, rv);
if (*_pageId != 0) {
return NS_OK;
}
// Create a new hidden, untyped and unvisited entry.
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
- "INSERT INTO moz_places (url, rev_host, hidden, frecency, guid) "
- "VALUES (:page_url, :rev_host, :hidden, :frecency, :guid) "
+ "INSERT INTO moz_places (url, url_hash, rev_host, hidden, frecency, guid) "
+ "VALUES (:page_url, hash(:page_url), :rev_host, :hidden, :frecency, :guid) "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
// host (reversed with trailing period)
nsAutoString revHost;
@@ -1024,17 +1024,18 @@ nsNavHistory::invalidateFrecencies(const
// Exclude place: queries by setting their frecency to zero.
nsCString invalidFrecenciesSQLFragment(
"UPDATE moz_places SET frecency = "
);
if (!aPlaceIdsQueryString.IsEmpty())
invalidFrecenciesSQLFragment.AppendLiteral("NOTIFY_FRECENCY(");
invalidFrecenciesSQLFragment.AppendLiteral(
"(CASE "
- "WHEN url BETWEEN 'place:' AND 'place;' "
+ "WHEN url_hash BETWEEN hash('place', 'prefix_lo') AND "
+ "hash('place', 'prefix_hi') "
"THEN 0 "
"ELSE -1 "
"END) "
);
if (!aPlaceIdsQueryString.IsEmpty()) {
invalidFrecenciesSQLFragment.AppendLiteral(
", url, guid, hidden, last_visit_date) "
);
@@ -1828,17 +1829,18 @@ PlacesSQLQueryBuilder::SelectAsSite()
"SELECT null, 'place:type=%ld&sort=%ld&domain=&domainIsHost=true'%s, "
":localhost, :localhost, null, null, null, null, null, null, null, "
"null, null, null "
"WHERE EXISTS ( "
"SELECT h.id FROM moz_places h "
"%s "
"WHERE h.hidden = 0 "
"AND h.visit_count > 0 "
- "AND h.url BETWEEN 'file://' AND 'file:/~' "
+ "AND h.url_hash BETWEEN hash('file', 'prefix_lo') AND "
+ "hash('file', 'prefix_hi') "
"%s "
"LIMIT 1 "
") "
"UNION ALL "
"SELECT null, "
"'place:type=%ld&sort=%ld&domain='||host||'&domainIsHost=true'%s, "
"host, host, null, null, null, null, null, null, null, "
"null, null, null "
@@ -2896,17 +2898,17 @@ nsNavHistory::GetPageTitle(nsIURI* aURI,
NS_ASSERTION(NS_IsMainThread(), "This can only be called on the main thread");
NS_ENSURE_ARG(aURI);
aTitle.Truncate(0);
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
"SELECT id, url, title, rev_host, visit_count, guid "
"FROM moz_places "
- "WHERE url = :page_url "
+ "WHERE url_hash = hash(:page_url) AND url = :page_url "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
bool hasResults = false;
@@ -3442,17 +3444,18 @@ nsNavHistory::QueryToSelectClause(nsNavH
clause.Str(",");
}
}
clause.Str(")");
}
if (excludeQueries) {
// Serching by terms implicitly exclude queries.
- clause.Condition("NOT h.url BETWEEN 'place:' AND 'place;'");
+ clause.Condition("NOT h.url_hash BETWEEN hash('place', 'prefix_lo') AND "
+ "hash('place', 'prefix_hi')");
}
clause.GetClauseString(*aClause);
return NS_OK;
}
// nsNavHistory::BindQueryClauseParameters
@@ -4196,17 +4199,17 @@ nsNavHistory::URIToResultNode(nsIURI* aU
"SELECT h.id, :page_url, COALESCE(b.title, h.title), "
"h.rev_host, h.visit_count, h.last_visit_date, f.url, "
"b.id, b.dateAdded, b.lastModified, b.parent, "
) + tagsFragment + NS_LITERAL_CSTRING(", h.frecency, h.hidden, h.guid, "
"null, null, null, b.guid, b.position, b.type, b.fk "
"FROM moz_places h "
"LEFT JOIN moz_bookmarks b ON b.fk = h.id "
"LEFT JOIN moz_favicons f ON h.favicon_id = f.id "
- "WHERE h.url = :page_url ")
+ "WHERE h.url_hash = hash(:page_url) AND h.url = :page_url ")
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
NS_ENSURE_SUCCESS(rv, rv);
bool hasMore = false;
@@ -4531,17 +4534,17 @@ nsNavHistory::AutoCompleteFeedback(int32
nsIAutoCompleteController *aController)
{
nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
"INSERT OR REPLACE INTO moz_inputhistory "
// use_count will asymptotically approach the max of 10.
"SELECT h.id, IFNULL(i.input, :input_text), IFNULL(i.use_count, 0) * .9 + 1 "
"FROM moz_places h "
"LEFT JOIN moz_inputhistory i ON i.place_id = h.id AND i.input = :input_text "
- "WHERE url = :page_url "
+ "WHERE url_hash = hash(:page_url) AND url = :page_url "
);
NS_ENSURE_STATE(stmt);
nsAutoString input;
nsresult rv = aController->GetSearchString(input);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("input_text"), input);
NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/places/nsNavHistoryResult.cpp
+++ b/toolkit/components/places/nsNavHistoryResult.cpp
@@ -186,17 +186,17 @@ nsNavHistoryResultNode::GetTags(nsAStrin
NS_ENSURE_STATE(DB);
nsCOMPtr<mozIStorageStatement> stmt = DB->GetStatement(
"/* do not warn (bug 487594) */ "
"SELECT GROUP_CONCAT(tag_title, ', ') "
"FROM ( "
"SELECT t.title AS tag_title "
"FROM moz_bookmarks b "
"JOIN moz_bookmarks t ON t.id = +b.parent "
- "WHERE b.fk = (SELECT id FROM moz_places WHERE url = :page_url) "
+ "WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url) "
"AND t.parent = :tags_folder "
"ORDER BY t.title COLLATE NOCASE ASC "
") "
);
NS_ENSURE_STATE(stmt);
mozStorageStatementScoper scoper(stmt);
nsNavHistory* history = nsNavHistory::GetHistoryService();
--- a/toolkit/components/places/nsPlacesAutoComplete.js
+++ b/toolkit/components/places/nsPlacesAutoComplete.js
@@ -379,17 +379,17 @@ function nsPlacesAutoComplete()
return this._db.createAsyncStatement(baseQuery("AND tags IS NOT NULL"));
});
XPCOMUtils.defineLazyGetter(this, "_openPagesQuery", function() {
return this._db.createAsyncStatement(
`SELECT t.url, t.url, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
:query_type, t.open_count, NULL
FROM moz_openpages_temp t
- LEFT JOIN moz_places h ON h.url = t.url
+ LEFT JOIN moz_places h ON h.url_hash = hash(t.url) AND h.url = t.url
WHERE h.id IS NULL
AND AUTOCOMPLETE_MATCH(:searchString, t.url, t.url, NULL,
NULL, NULL, NULL, t.open_count,
:matchBehavior, :searchBehavior)
ORDER BY t.ROWID DESC
LIMIT :maxResults`
);
});
--- a/toolkit/components/places/nsPlacesExpiration.js
+++ b/toolkit/components/places/nsPlacesExpiration.js
@@ -21,31 +21,34 @@
*/
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
////////////////////////////////////////////////////////////////////////////////
//// Constants
// Last expiration step should run before the final sync.
const TOPIC_SHUTDOWN = "places-will-close-connection";
const TOPIC_PREF_CHANGED = "nsPref:changed";
const TOPIC_DEBUG_START_EXPIRATION = "places-debug-start-expiration";
const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
const TOPIC_IDLE_BEGIN = "idle";
const TOPIC_IDLE_END = "active";
const TOPIC_IDLE_DAILY = "idle-daily";
+const TOPIC_TESTING_MODE = "testing-mode";
+const TOPIC_TEST_INTERVAL_CHANGED = "test-interval-changed";
// Branch for all expiration preferences.
const PREF_BRANCH = "places.history.expiration.";
// Max number of unique URIs to retain in history.
// Notice this is a lazy limit. This means we will start to expire if we will
// go over it, but we won't ensure that we will stop exactly when we reach it,
// instead we will stop after the next expiration step that will bring us
@@ -67,17 +70,17 @@ const PREF_INTERVAL_SECONDS_NOTSET = 3 *
// This percentage of memory size is used to protect against calculating a too
// large database size on systems with small memory.
const DATABASE_TO_MEMORY_PERC = 4;
// This percentage of disk size is used to protect against calculating a too
// large database size on disks with tiny quota or available space.
const DATABASE_TO_DISK_PERC = 2;
// Maximum size of the optimal database. High-end hardware has plenty of
// memory and disk space, but performances don't grow linearly.
-const DATABASE_MAX_SIZE = 167772160; // 160MiB
+const DATABASE_MAX_SIZE = 62914560; // 60MiB
// If the physical memory size is bogus, fallback to this.
const MEMSIZE_FALLBACK_BYTES = 268435456; // 256 MiB
// If the disk available space is bogus, fallback to this.
const DISKSIZE_FALLBACK_BYTES = 268435456; // 256 MiB
// Max number of entries to expire at each expiration step.
// This value is globally used for different kind of data we expire, can be
// tweaked based on data type. See below in getBoundStatement.
@@ -95,19 +98,18 @@ const EXPIRE_LIMIT_PER_LARGE_STEP_MULTIP
// 2. Dirty history:
// We expire at the default interval, but a greater number of entries
// (default number of entries * EXPIRE_AGGRESSIVITY_MULTIPLIER).
const EXPIRE_AGGRESSIVITY_MULTIPLIER = 3;
// This is the average size in bytes of an URI entry in the database.
// Magic numbers are determined through analysis of the distribution of a ratio
// between number of unique URIs and database size among our users.
-// Based on these values we evaluate how many unique URIs we can handle before
-// starting expiring some.
-const URIENTRY_AVG_SIZE = 1600;
+// Used as a fall back value when it's not possible to calculate the real value.
+const URIENTRY_AVG_SIZE = 600;
// Seconds of idle time before starting a larger expiration step.
// Notice during idle we stop the expiration timer since we don't want to hurt
// stand-by or mobile devices batteries.
const IDLE_TIMEOUT_SECONDS = 5 * 60;
// If a clear history ran just before we shutdown, we will skip most of the
// expiration at shutdown. This is maximum number of seconds from last
@@ -477,55 +479,51 @@ function nsPlacesExpiration()
, reason TEXT NOT NULL DEFAULT "expired"
)`);
stmt.executeAsync();
stmt.finalize();
return db;
});
- XPCOMUtils.defineLazyServiceGetter(this, "_sys",
- "@mozilla.org/system-info;1",
- "nsIPropertyBag2");
XPCOMUtils.defineLazyServiceGetter(this, "_idle",
"@mozilla.org/widget/idleservice;1",
"nsIIdleService");
this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefService).
getBranch(PREF_BRANCH);
- this._loadPrefs();
- // Observe our preferences branch for changes.
- this._prefBranch.addObserver("", this, false);
+ this._loadPrefs().then(() => {
+ // Observe our preferences branch for changes.
+ this._prefBranch.addObserver("", this, true);
+
+ // Create our expiration timer.
+ this._newTimer();
+ }, Cu.reportError);
// Register topic observers.
- Services.obs.addObserver(this, TOPIC_SHUTDOWN, false);
- Services.obs.addObserver(this, TOPIC_DEBUG_START_EXPIRATION, false);
- Services.obs.addObserver(this, TOPIC_IDLE_DAILY, false);
-
- // Create our expiration timer.
- this._newTimer();
+ Services.obs.addObserver(this, TOPIC_SHUTDOWN, true);
+ Services.obs.addObserver(this, TOPIC_DEBUG_START_EXPIRATION, true);
+ Services.obs.addObserver(this, TOPIC_IDLE_DAILY, true);
}
nsPlacesExpiration.prototype = {
//////////////////////////////////////////////////////////////////////////////
//// nsIObserver
observe: function PEX_observe(aSubject, aTopic, aData)
{
+ if (this._shuttingDown) {
+ return;
+ }
+
if (aTopic == TOPIC_SHUTDOWN) {
this._shuttingDown = true;
- Services.obs.removeObserver(this, TOPIC_SHUTDOWN);
- Services.obs.removeObserver(this, TOPIC_DEBUG_START_EXPIRATION);
- Services.obs.removeObserver(this, TOPIC_IDLE_DAILY);
-
- this._prefBranch.removeObserver("", this);
-
this.expireOnIdle = false;
if (this._timer) {
this._timer.cancel();
this._timer = null;
}
// If we didn't ran a clearHistory recently and database is dirty, we
@@ -535,22 +533,22 @@ nsPlacesExpiration.prototype = {
SHUTDOWN_WITH_RECENT_CLEARHISTORY_TIMEOUT_SECONDS * 1000;
if (!hasRecentClearHistory && this.status == STATUS.DIRTY) {
this._expireWithActionAndLimit(ACTION.SHUTDOWN_DIRTY, LIMIT.LARGE);
}
this._finalizeInternalStatements();
}
else if (aTopic == TOPIC_PREF_CHANGED) {
- this._loadPrefs();
-
- if (aData == PREF_INTERVAL_SECONDS) {
- // Renew the timer with the new interval value.
- this._newTimer();
- }
+ this._loadPrefs().then(() => {
+ if (aData == PREF_INTERVAL_SECONDS) {
+ // Renew the timer with the new interval value.
+ this._newTimer();
+ }
+ }, Cu.reportError);
}
else if (aTopic == TOPIC_DEBUG_START_EXPIRATION) {
// The passed-in limit is the maximum number of visits to expire when
// history is over capacity. Mind to correctly handle the NaN value.
let limit = parseInt(aData);
if (limit == -1) {
// Everything should be expired without any limit. If history is over
// capacity then all existing visits will be expired.
@@ -585,16 +583,19 @@ nsPlacesExpiration.prototype = {
else if (aTopic == TOPIC_IDLE_END) {
// Restart the expiration timer.
if (!this._timer)
this._newTimer();
}
else if (aTopic == TOPIC_IDLE_DAILY) {
this._expireWithActionAndLimit(ACTION.IDLE_DAILY, LIMIT.LARGE);
}
+ else if (aTopic == TOPIC_TESTING_MODE) {
+ this._testingMode = true;
+ }
},
//////////////////////////////////////////////////////////////////////////////
//// nsINavHistoryObserver
_inBatchMode: false,
onBeginUpdateBatch: function PEX_onBeginUpdateBatch()
{
@@ -812,35 +813,37 @@ nsPlacesExpiration.prototype = {
else
this._expireOnIdle = aExpireOnIdle;
return this._expireOnIdle;
},
get expireOnIdle() {
return this._expireOnIdle;
},
- _loadPrefs: function PEX__loadPrefs() {
+ _loadPrefs: Task.async(function* () {
// Get the user's limit, if it was set.
try {
// We want to silently fail since getIntPref throws if it does not exist,
// and use a default to fallback to.
this._urisLimit = this._prefBranch.getIntPref(PREF_MAX_URIS);
- }
- catch(e) {}
+ } catch(ex) { /* User limit not set */ }
if (this._urisLimit < 0) {
- // The preference did not exist or has a negative value.
- // Calculate the number of unique places that may fit an optimal database
- // size on this hardware. If there are more than these unique pages,
- // some will be expired.
+ // Some testing code expects a pref change to be synchronous, so
+ // temporarily set this to a large value, while we asynchronously update
+ // to the correct value.
+ this._urisLimit = 300000;
+ // The user didn't specify a custom limit, so we calculate the number of
+ // unique places that may fit an optimal database size on this hardware.
+ // Oldest pages over this threshold will be expired.
let memSizeBytes = MEMSIZE_FALLBACK_BYTES;
try {
// Limit the size on systems with small memory.
- memSizeBytes = this._sys.getProperty("memsize");
+ memSizeBytes = Services.sysinfo.getProperty("memsize");
} catch (ex) {}
if (memSizeBytes <= 0) {
memsize = MEMSIZE_FALLBACK_BYTES;
}
let diskAvailableBytes = DISKSIZE_FALLBACK_BYTES;
try {
// Protect against a full disk or tiny quota.
@@ -853,33 +856,46 @@ nsPlacesExpiration.prototype = {
}
let optimalDatabaseSize = Math.min(
memSizeBytes * DATABASE_TO_MEMORY_PERC / 100,
diskAvailableBytes * DATABASE_TO_DISK_PERC / 100,
DATABASE_MAX_SIZE
);
- this._urisLimit = Math.ceil(optimalDatabaseSize / URIENTRY_AVG_SIZE);
+ // Calculate avg size of a URI in the database.
+ let db = yield PlacesUtils.promiseDBConnection();
+ let pageSize = (yield db.execute(`PRAGMA page_size`))[0].getResultByIndex(0);
+ let pageCount = (yield db.execute(`PRAGMA page_count`))[0].getResultByIndex(0);
+ let freelistCount = (yield db.execute(`PRAGMA freelist_count`))[0].getResultByIndex(0);
+ let dbSize = (pageCount - freelistCount) * pageSize;
+ let uriCount = (yield db.execute(`SELECT count(*) FROM moz_places`))[0].getResultByIndex(0);
+ let avgURISize = Math.ceil(dbSize / uriCount);
+ // For new profiles this value may be too large, due to the Sqlite header,
+ // or Infinity when there are no pages. Thus we must limit it.
+ if (avgURISize > (URIENTRY_AVG_SIZE * 3)) {
+ avgURISize = URIENTRY_AVG_SIZE;
+ }
+ this._urisLimit = Math.ceil(optimalDatabaseSize / avgURISize);
}
// Expose the calculated limit to other components.
this._prefBranch.setIntPref(PREF_READONLY_CALCULATED_MAX_URIS,
this._urisLimit);
// Get the expiration interval value.
try {
// We want to silently fail since getIntPref throws if it does not exist,
// and use a default to fallback to.
this._interval = this._prefBranch.getIntPref(PREF_INTERVAL_SECONDS);
+ } catch (ex) { /* User interval not set */ }
+ if (this._interval <= 0) {
+ this._interval = PREF_INTERVAL_SECONDS_NOTSET;
}
- catch (e) {}
- if (this._interval <= 0)
- this._interval = PREF_INTERVAL_SECONDS_NOTSET;
- },
+ }),
/**
* Evaluates the real number of pages in the database and the value currently
* used by the SQLite query planner.
*
* @param aCallback
* invoked on success, function (aPagesCount, aStatsCount).
*/
@@ -1064,31 +1080,36 @@ nsPlacesExpiration.prototype = {
if (this._shuttingDown)
return undefined;
let interval = this.status != STATUS.DIRTY ?
this._interval * EXPIRE_AGGRESSIVITY_MULTIPLIER : this._interval;
let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(this, interval * 1000,
Ci.nsITimer.TYPE_REPEATING_SLACK);
+ if (this._testingMode) {
+ Services.obs.notifyObservers(null, TOPIC_TEST_INTERVAL_CHANGED,
+ interval);
+ }
return this._timer = timer;
},
//////////////////////////////////////////////////////////////////////////////
//// nsISupports
classID: Components.ID("705a423f-2f69-42f3-b9fe-1517e0dee56f"),
_xpcom_factory: XPCOMUtils.generateSingletonFactory(nsPlacesExpiration),
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIObserver
, Ci.nsINavHistoryObserver
, Ci.nsITimerCallback
, Ci.mozIStorageStatementCallback
+ , Ci.nsISupportsWeakReference
])
};
////////////////////////////////////////////////////////////////////////////////
//// Module Registration
var components = [nsPlacesExpiration];
this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/toolkit/components/places/nsPlacesIndexes.h
+++ b/toolkit/components/places/nsPlacesIndexes.h
@@ -11,19 +11,19 @@
NS_LITERAL_CSTRING( \
"CREATE " __type " INDEX IF NOT EXISTS " __table "_" __name \
" ON " __table " (" __columns ")" \
)
/**
* moz_places
*/
-#define CREATE_IDX_MOZ_PLACES_URL \
+#define CREATE_IDX_MOZ_PLACES_URL_HASH \
CREATE_PLACES_IDX( \
- "url_uniqueindex", "moz_places", "url", "UNIQUE" \
+ "url_hashindex", "moz_places", "url_hash", "" \
)
#define CREATE_IDX_MOZ_PLACES_FAVICON \
CREATE_PLACES_IDX( \
"faviconindex", "moz_places", "favicon_id", "" \
)
#define CREATE_IDX_MOZ_PLACES_REVHOST \
--- a/toolkit/components/places/nsPlacesTables.h
+++ b/toolkit/components/places/nsPlacesTables.h
@@ -17,16 +17,17 @@
", visit_count INTEGER DEFAULT 0" \
", hidden INTEGER DEFAULT 0 NOT NULL" \
", typed INTEGER DEFAULT 0 NOT NULL" \
", favicon_id INTEGER" \
", frecency INTEGER DEFAULT -1 NOT NULL" \
", last_visit_date INTEGER " \
", guid TEXT" \
", foreign_count INTEGER DEFAULT 0 NOT NULL" \
+ ", url_hash INTEGER DEFAULT 0 NOT NULL " \
")" \
)
#define CREATE_MOZ_HISTORYVISITS NS_LITERAL_CSTRING( \
"CREATE TABLE moz_historyvisits (" \
" id INTEGER PRIMARY KEY" \
", from_visit INTEGER" \
", place_id INTEGER" \
--- a/toolkit/components/places/nsTaggingService.js
+++ b/toolkit/components/places/nsTaggingService.js
@@ -61,17 +61,17 @@ TaggingService.prototype = {
return -1;
// Using bookmarks service API for this would be a pain.
// Until tags implementation becomes sane, go the query way.
let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
.DBConnection;
let stmt = db.createStatement(
`SELECT id FROM moz_bookmarks
WHERE parent = :tag_id
- AND fk = (SELECT id FROM moz_places WHERE url = :page_url)`
+ AND fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url)`
);
stmt.params.tag_id = tagId;
stmt.params.page_url = aURI.spec;
try {
if (stmt.executeStep()) {
return stmt.row.id;
}
}
@@ -375,17 +375,17 @@ TaggingService.prototype = {
// Using bookmarks service API for this would be a pain.
// Until tags implementation becomes sane, go the query way.
let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
.DBConnection;
let stmt = db.createStatement(
`SELECT id, parent
FROM moz_bookmarks
- WHERE fk = (SELECT id FROM moz_places WHERE url = :page_url)`
+ WHERE fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url)`
);
stmt.params.page_url = aURI.spec;
try {
while (stmt.executeStep() && !isBookmarked) {
if (this._tagFolders[stmt.row.parent]) {
// This is a tag entry.
itemIds.push(stmt.row.id);
}
--- a/toolkit/components/places/tests/PlacesTestUtils.jsm
+++ b/toolkit/components/places/tests/PlacesTestUtils.jsm
@@ -59,17 +59,17 @@ this.PlacesTestUtils = Object.freeze({
} else if (place.uri instanceof URL) {
place.uri = NetUtil.newURI(place.uri.href);
}
if (typeof place.title != "string") {
place.title = "test visit for " + place.uri.spec;
}
if (typeof place.referrer == "string") {
place.referrer = NetUtil.newURI(place.referrer);
- } else if (place.referrer instanceof URL) {
+ } else if (place.referrer && place.referrer instanceof URL) {
place.referrer = NetUtil.newURI(place.referrer.href);
}
place.visits = [{
transitionType: place.transition === undefined ? Ci.nsINavHistoryService.TRANSITION_LINK
: place.transition,
visitDate: place.visitDate || (now++) * 1000,
referrerURI: place.referrer
}];
@@ -144,17 +144,17 @@ this.PlacesTestUtils = Object.freeze({
* @return {Promise}
* @resolves Returns true if the page is found.
* @rejects JavaScript exception.
*/
isPageInDB: Task.async(function* (aURI) {
let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.executeCached(
- "SELECT id FROM moz_places WHERE url = :url",
+ "SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url",
{ url });
return rows.length > 0;
}),
/**
* Asynchronously checks how many visits exist for a specified page.
* @param aURI
* nsIURI or address to look for.
@@ -164,13 +164,13 @@ this.PlacesTestUtils = Object.freeze({
* @rejects JavaScript exception.
*/
visitsInDB: Task.async(function* (aURI) {
let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.executeCached(
`SELECT count(*) FROM moz_historyvisits v
JOIN moz_places h ON h.id = v.place_id
- WHERE url = :url`,
+ WHERE url_hash = hash(:url) AND url = :url`,
{ url });
return rows[0].getResultByIndex(0);
})
});
--- a/toolkit/components/places/tests/bookmarks/test_1017502-bookmarks_foreign_count.js
+++ b/toolkit/components/places/tests/bookmarks/test_1017502-bookmarks_foreign_count.js
@@ -10,17 +10,18 @@ added or removed and also the maintenanc
*/
const T_URI = NetUtil.newURI("https://www.mozilla.org/firefox/nightly/firstrun/");
function* getForeignCountForURL(conn, url) {
yield PlacesTestUtils.promiseAsyncUpdates();
url = url instanceof Ci.nsIURI ? url.spec : url;
let rows = yield conn.executeCached(
- "SELECT foreign_count FROM moz_places WHERE url = :t_url ", { t_url: url });
+ `SELECT foreign_count FROM moz_places WHERE url_hash = hash(:t_url)
+ AND url = :t_url`, { t_url: url });
return rows[0].getResultByName("foreign_count");
}
function run_test() {
run_next_test();
}
add_task(function* add_remove_change_bookmark_test() {
@@ -63,17 +64,18 @@ add_task(function* maintenance_foreign_c
let conn = yield PlacesUtils.promiseDBConnection();
// Simulate a visit to the url
yield PlacesTestUtils.addVisits(T_URI);
// Adjust the foreign_count for the added entry to an incorrect value
let deferred = Promise.defer();
let stmt = DBConn().createAsyncStatement(
- "UPDATE moz_places SET foreign_count = 10 WHERE url = :t_url ");
+ `UPDATE moz_places SET foreign_count = 10 WHERE url_hash = hash(:t_url)
+ AND url = :t_url `);
stmt.params.t_url = T_URI.spec;
stmt.executeAsync({
handleCompletion: function(){
deferred.resolve();
}
});
stmt.finalize();
yield deferred.promise;
--- a/toolkit/components/places/tests/browser/browser_bug248970.js
+++ b/toolkit/components/places/tests/browser/browser_bug248970.js
@@ -115,45 +115,24 @@ function getPlacesItemsCount() {
return cc;
}
function* checkHistoryItems() {
for (let i = 0; i < visitedURIs.length; i++) {
let visitedUri = visitedURIs[i];
ok((yield promiseIsURIVisited(visitedUri)), "");
if (/embed/.test(visitedUri.spec)) {
- is(!!pageInDatabase(visitedUri), false, "Check if URI is in database");
+ is((yield PlacesTestUtils.isPageInDB(visitedUri)), false, "Check if URI is in database");
} else {
- ok(!!pageInDatabase(visitedUri), "Check if URI is in database");
+ ok((yield PlacesTestUtils.isPageInDB(visitedUri)), "Check if URI is in database");
}
}
}
/**
- * Checks if an address is found in the database.
- * @param aURI
- * nsIURI or address to look for.
- * @return place id of the page or 0 if not found
- */
-function pageInDatabase(aURI) {
- let url = (aURI instanceof Ci.nsIURI ? aURI.spec : aURI);
- let stmt = DBConn().createStatement(
- "SELECT id FROM moz_places WHERE url = :url"
- );
- stmt.params.url = url;
- try {
- if (!stmt.executeStep())
- return 0;
- return stmt.getInt64(0);
- } finally {
- stmt.finalize();
- }
-}
-
-/**
* Function attempts to check if Bookmark-A has been visited
* during private browsing mode, function should return false
*
* @returns false if the accessCount has not changed
* true if the accessCount has changed
*/
function isBookmarkAltered(){
let options = PlacesUtils.history.getNewQueryOptions();
--- a/toolkit/components/places/tests/browser/browser_double_redirect.js
+++ b/toolkit/components/places/tests/browser/browser_double_redirect.js
@@ -29,17 +29,17 @@ add_task(function* () {
// Get all pages visited from the original typed one
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.execute(
`SELECT url FROM moz_historyvisits
JOIN moz_places h ON h.id = place_id
WHERE from_visit IN
(SELECT v.id FROM moz_historyvisits v
JOIN moz_places p ON p.id = v.place_id
- WHERE p.url = :url)
+ WHERE p.url_hash = hash(:url) AND p.url = :url)
`, { url: TEST_URI.spec });
is(rows.length, 1, "Found right number of visits");
let visitedUrl = rows[0].getResultByName("url");
// Check that redirect from_visit is not from the original typed one
is(visitedUrl, FIRST_REDIRECTING_URI.spec, "Check referrer for " + visitedUrl);
resolve();
--- a/toolkit/components/places/tests/browser/browser_history_post.js
+++ b/toolkit/components/places/tests/browser/browser_history_post.js
@@ -13,16 +13,11 @@ add_task(function* () {
resolve();
});
});
submit.click();
yield p;
});
let visited = yield promiseIsURIVisited(SJS_URI);
ok(!visited, "The POST page should not be added to history");
- let db = yield PlacesUtils.promiseDBConnection();
- let rows = yield db.execute(
- "SELECT 1 FROM moz_places WHERE url = :page_url",
- {page_url: SJS_URI.spec});
- is(rows.length, 0, "The page should not be in the database");
- yield db.close();
+ ok(!(yield PlacesTestUtils.isPageInDB(SJS_URI.spec)), "The page should not be in the database");
}));
});
--- a/toolkit/components/places/tests/browser/browser_settitle.js
+++ b/toolkit/components/places/tests/browser/browser_settitle.js
@@ -1,25 +1,19 @@
-/**
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
var conn = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
/**
* Gets a single column value from either the places or historyvisits table.
*/
-function getColumn(table, column, fromColumnName, fromColumnValue)
+function getColumn(table, column, url)
{
var stmt = conn.createStatement(
- `SELECT ${column} FROM ${table} WHERE ${fromColumnName} = :val
- LIMIT 1`);
+ `SELECT ${column} FROM ${table} WHERE url_hash = hash(:val) AND url = :val`);
try {
- stmt.params.val = fromColumnValue;
+ stmt.params.val = url;
stmt.executeStep();
return stmt.row[column];
}
finally {
stmt.finalize();
}
}
@@ -64,19 +58,19 @@ add_task(function* ()
const url2 = "http://example.com/tests/toolkit/components/places/tests/browser/title2.html";
let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
BrowserTestUtils.loadURI(gBrowser.selectedBrowser, url2);
yield loadPromise;
let data = yield titleChangedPromise;
is(data[0].uri.spec, "http://example.com/tests/toolkit/components/places/tests/browser/title2.html");
is(data[0].title, "Some title");
- is(data[0].guid, getColumn("moz_places", "guid", "url", data[0].uri.spec));
+ is(data[0].guid, getColumn("moz_places", "guid", data[0].uri.spec));
data.forEach(function(item) {
- var title = getColumn("moz_places", "title", "url", data[0].uri.spec);
+ var title = getColumn("moz_places", "title", data[0].uri.spec);
is(title, item.title);
});
gBrowser.removeCurrentTab();
yield PlacesTestUtils.clearHistory();
});
--- a/toolkit/components/places/tests/browser/browser_visituri.js
+++ b/toolkit/components/places/tests/browser/browser_visituri.js
@@ -1,13 +1,8 @@
-/**
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
/**
* One-time observer callback.
*/
function promiseObserve(name, checkFn) {
return new Promise(resolve => {
Services.obs.addObserver(function observer(subject) {
if (checkFn(subject)) {
Services.obs.removeObserver(observer, name);
@@ -21,16 +16,17 @@ var conn = PlacesUtils.history.QueryInte
/**
* Gets a single column value from either the places or historyvisits table.
*/
function getColumn(table, column, fromColumnName, fromColumnValue) {
let sql = `SELECT ${column}
FROM ${table}
WHERE ${fromColumnName} = :val
+ ${fromColumnName == "url" ? "AND url_hash = hash(:val)" : ""}
LIMIT 1`;
let stmt = conn.createStatement(sql);
try {
stmt.params.val = fromColumnValue;
ok(stmt.executeStep(), "Expect to get a row");
return stmt.row[column];
}
finally {
--- a/toolkit/components/places/tests/browser/head.js
+++ b/toolkit/components/places/tests/browser/head.js
@@ -23,17 +23,17 @@ const TRANSITION_DOWNLOAD = PlacesUtils.
* param aCallback
* Callback function that will get the property value.
*/
function fieldForUrl(aURI, aFieldName, aCallback)
{
let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
let stmt = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
.DBConnection.createAsyncStatement(
- `SELECT ${aFieldName} FROM moz_places WHERE url = :page_url`
+ `SELECT ${aFieldName} FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url`
);
stmt.params.page_url = url;
stmt.executeAsync({
_value: -1,
handleResult: function(aResultSet) {
let row = aResultSet.getNextRow();
if (!row)
ok(false, "The page should exist in the database");
@@ -211,17 +211,17 @@ function checkGuidForURI(aURI, aGUID) {
* @param aURI
* The uri to check.
* @return the associated the guid.
*/
function doGetGuidForURI(aURI) {
let stmt = DBConn().createStatement(
`SELECT guid
FROM moz_places
- WHERE url = :url`
+ WHERE url_hash = hash(:url) AND url = :url`
);
stmt.params.url = aURI.spec;
ok(stmt.executeStep(), "Check get guid for uri from moz_places");
let guid = stmt.row.guid;
stmt.finalize();
doCheckValidPlacesGuid(guid);
return guid;
}
--- a/toolkit/components/places/tests/cpp/places_test_harness.h
+++ b/toolkit/components/places/tests/cpp/places_test_harness.h
@@ -264,17 +264,17 @@ do_get_place(nsIURI* aURI, PlaceRecord&
nsCOMPtr<mozIStorageStatement> stmt;
nsCString spec;
nsresult rv = aURI->GetSpec(spec);
do_check_success(rv);
rv = dbConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT id, hidden, typed, visit_count, guid FROM moz_places "
- "WHERE url=?1 "
+ "WHERE url_hash = hash(?1) AND url = ?1"
), getter_AddRefs(stmt));
do_check_success(rv);
rv = stmt->BindUTF8StringByIndex(0, spec);
do_check_success(rv);
bool hasResults;
rv = stmt->ExecuteStep(&hasResults);
--- a/toolkit/components/places/tests/expiration/head_expiration.js
+++ b/toolkit/components/places/tests/expiration/head_expiration.js
@@ -29,17 +29,19 @@ function shutdownExpiration()
}
/**
* Causes expiration component to start, otherwise it would wait for the first
* history notification.
*/
function force_expiration_start() {
- Cc["@mozilla.org/places/expiration;1"].getService(Ci.nsISupports);
+ Cc["@mozilla.org/places/expiration;1"]
+ .getService(Ci.nsIObserver)
+ .observe(null, "testing-mode", null);
}
/**
* Forces an expiration run.
*
* @param [optional] aLimit
* Limit for the expiration. Pass -1 for unlimited.
--- a/toolkit/components/places/tests/expiration/test_annos_expire_policy.js
+++ b/toolkit/components/places/tests/expiration/test_annos_expire_policy.js
@@ -47,17 +47,17 @@ function add_old_anno(aIdentifier, aName
}
else if (aIdentifier instanceof Ci.nsIURI){
// Page annotation.
as.setPageAnnotation(aIdentifier, aName, aValue, 0, aExpirePolicy);
// Update dateAdded for the last added annotation.
sql = "UPDATE moz_annos SET dateAdded = :expire_date, lastModified = :last_modified " +
"WHERE id = (SELECT a.id FROM moz_annos a " +
"LEFT JOIN moz_places h on h.id = a.place_id " +
- "WHERE h.url = :id " +
+ "WHERE h.url_hash = hash(:id) AND h.url = :id " +
"ORDER BY a.dateAdded DESC LIMIT 1)";
}
else
do_throw("Wrong identifier type");
let stmt = DBConn().createStatement(sql);
stmt.params.id = (typeof(aIdentifier) == "number") ? aIdentifier
: aIdentifier.spec;
--- a/toolkit/components/places/tests/expiration/test_clearHistory.js
+++ b/toolkit/components/places/tests/expiration/test_clearHistory.js
@@ -50,17 +50,17 @@ function add_old_anno(aIdentifier, aName
// Page annotation.
as.setPageAnnotation(aIdentifier, aName, aValue, 0, aExpirePolicy);
// Update dateAdded for the last added annotation.
sql = "UPDATE moz_annos SET dateAdded = :expire_date, lastModified = :last_modified " +
"WHERE id = ( " +
"SELECT a.id FROM moz_annos a " +
"JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " +
"JOIN moz_places h on h.id = a.place_id " +
- "WHERE h.url = :id " +
+ "WHERE h.url_hash = hash(:id) AND h.url = :id " +
"AND n.name = :anno_name " +
"ORDER BY a.dateAdded DESC LIMIT 1 " +
")";
}
else
do_throw("Wrong identifier type");
let stmt = DBConn().createStatement(sql);
--- a/toolkit/components/places/tests/expiration/test_outdated_analyze.js
+++ b/toolkit/components/places/tests/expiration/test_outdated_analyze.js
@@ -47,17 +47,17 @@ function run_test() {
Services.obs.addObserver(function observeExpiration(aSubject, aTopic, aData) {
Services.obs.removeObserver(observeExpiration,
PlacesUtils.TOPIC_EXPIRATION_FINISHED);
// Check that statistica are up-to-date.
let stmt = DBConn().createAsyncStatement(
"SELECT (SELECT COUNT(*) FROM moz_places) - "
+ "(SELECT SUBSTR(stat,1,LENGTH(stat)-2) FROM sqlite_stat1 "
- + "WHERE idx = 'moz_places_url_uniqueindex')"
+ + "WHERE idx = 'moz_places_url_hashindex')"
);
stmt.executeAsync({
handleResult: function(aResultSet) {
let row = aResultSet.getNextRow();
this._difference = row.getResultByIndex(0);
},
handleError: function(aError) {
do_throw("Unexpected error (" + aError.result + "): " + aError.message);
--- a/toolkit/components/places/tests/expiration/test_pref_interval.js
+++ b/toolkit/components/places/tests/expiration/test_pref_interval.js
@@ -1,14 +1,8 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
/**
* What this is aimed to test:
*
* Expiration relies on an interval, that is user-preffable setting
* "places.history.expiration.interval_seconds".
* On pref change it will stop current interval timer and fire a new one,
* that will obey the new value.
* If the pref is set to a number <= 0 we will use the default value.
@@ -16,56 +10,16 @@
// Default timer value for expiration in seconds. Must have same value as
// PREF_INTERVAL_SECONDS_NOTSET in nsPlacesExpiration.
const DEFAULT_TIMER_DELAY_SECONDS = 3 * 60;
// Sync this with the const value in the component.
const EXPIRE_AGGRESSIVITY_MULTIPLIER = 3;
-Cu.import("resource://testing-common/MockRegistrar.jsm");
-// Provide a mock timer implementation, so there is no need to wait seconds to
-// achieve test results.
-const TIMER_CONTRACT_ID = "@mozilla.org/timer;1";
-var mockCID;
-
-var mockTimerImpl = {
- initWithCallback: function MTI_initWithCallback(aCallback, aDelay, aType) {
- print("Checking timer delay equals expected interval value");
- if (!currentTest)
- return;
- // History status is not dirty, so the timer is delayed.
- do_check_eq(aDelay, currentTest.expectedTimerDelay * 1000 * EXPIRE_AGGRESSIVITY_MULTIPLIER)
-
- do_execute_soon(runNextTest);
- },
-
- cancel: function() {},
- initWithFuncCallback: function() {},
- init: function() {},
-
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsITimer,
- ])
-}
-
-function replace_timer_factory() {
- mockCID = MockRegistrar.register(TIMER_CONTRACT_ID, mockTimerImpl);
-}
-
-do_register_cleanup(function() {
- // Shutdown expiration before restoring original timer, otherwise we could
- // leak due to the different implementation.
- shutdownExpiration();
-
- // Restore original timer factory.
- MockRegistrar.unregister(mockCID);
-});
-
-
var tests = [
// This test should be the first, so the interval won't be influenced by
// status of history.
{ desc: "Set interval to 1s.",
interval: 1,
expectedTimerDelay: 1
},
@@ -84,37 +38,26 @@ var tests = [
interval: 100,
expectedTimerDelay: 100
},
];
var currentTest;
-function run_test() {
+add_task(function* test() {
// The pref should not exist by default.
- try {
- getInterval();
- do_throw("interval pref should not exist by default");
- }
- catch (ex) {}
-
- // Use our own mock timer implementation.
- replace_timer_factory();
+ Assert.throws(() => getInterval());
// Force the component, so it will start observing preferences.
force_expiration_start();
- runNextTest();
- do_test_pending();
-}
-
-function runNextTest() {
- if (tests.length) {
- currentTest = tests.shift();
+ for (let currentTest of tests) {
print(currentTest.desc);
+ let promise = promiseTopicObserved("test-interval-changed");
setInterval(currentTest.interval);
+ let [, data] = yield promise;
+ Assert.equal(data, currentTest.expectedTimerDelay * EXPIRE_AGGRESSIVITY_MULTIPLIER);
}
- else {
- clearInterval();
- do_test_finished();
- }
-}
+
+ clearInterval();
+});
+
--- a/toolkit/components/places/tests/expiration/test_pref_maxpages.js
+++ b/toolkit/components/places/tests/expiration/test_pref_maxpages.js
@@ -83,17 +83,17 @@ add_task(function* test_pref_maxpages()
// Setup visits.
let now = getExpirablePRTime();
for (let i = 0; i < currentTest.addPages; i++) {
let page = "http://" + testIndex + "." + i + ".mozilla.org/";
yield PlacesTestUtils.addVisits({ uri: uri(page), visitDate: now++ });
}
// Observe history.
- historyObserver = {
+ let historyObserver = {
onBeginUpdateBatch: function PEX_onBeginUpdateBatch() {},
onEndUpdateBatch: function PEX_onEndUpdateBatch() {},
onClearHistory: function() {},
onVisit: function() {},
onTitleChanged: function() {},
onDeleteURI: function(aURI) {
print("onDeleteURI " + aURI.spec);
currentTest.receivedNotifications++;
--- a/toolkit/components/places/tests/expiration/xpcshell.ini
+++ b/toolkit/components/places/tests/expiration/xpcshell.ini
@@ -13,11 +13,9 @@ skip-if = os == "android"
[test_clearHistory.js]
[test_debug_expiration.js]
[test_idle_daily.js]
[test_notifications.js]
[test_notifications_onDeleteURI.js]
[test_notifications_onDeleteVisits.js]
[test_outdated_analyze.js]
[test_pref_interval.js]
-# Crashes when timer is used on non-main thread due to JS implemetation in this test
-skip-if = "JS implementation of nsITimer"
[test_pref_maxpages.js]
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -1,14 +1,14 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-const CURRENT_SCHEMA_VERSION = 32;
+const CURRENT_SCHEMA_VERSION = 33;
const FIRST_UPGRADABLE_SCHEMA_VERSION = 11;
const NS_APP_USER_PROFILE_50_DIR = "ProfD";
const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
// Shortcuts to transitions type.
const TRANSITION_LINK = Ci.nsINavHistoryService.TRANSITION_LINK;
const TRANSITION_TYPED = Ci.nsINavHistoryService.TRANSITION_TYPED;
@@ -294,17 +294,17 @@ function dump_table(aName)
* @param aURI
* nsIURI or address to look for.
* @return place id of the page or 0 if not found
*/
function page_in_database(aURI)
{
let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
let stmt = DBConn().createStatement(
- "SELECT id FROM moz_places WHERE url = :url"
+ "SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url"
);
stmt.params.url = url;
try {
if (!stmt.executeStep())
return 0;
return stmt.getInt64(0);
}
finally {
@@ -319,17 +319,17 @@ function page_in_database(aURI)
* @return number of visits found.
*/
function visits_in_database(aURI)
{
let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
let stmt = DBConn().createStatement(
`SELECT count(*) FROM moz_historyvisits v
JOIN moz_places h ON h.id = v.place_id
- WHERE url = :url`
+ WHERE url_hash = hash(:url) AND url = :url`
);
stmt.params.url = url;
try {
if (!stmt.executeStep())
return 0;
return stmt.getInt64(0);
}
finally {
@@ -537,17 +537,17 @@ function frecencyForUrl(aURI)
{
let url = aURI;
if (aURI instanceof Ci.nsIURI) {
url = aURI.spec;
} else if (aURI instanceof URL) {
url = aURI.href;
}
let stmt = DBConn().createStatement(
- "SELECT frecency FROM moz_places WHERE url = ?1"
+ "SELECT frecency FROM moz_places WHERE url_hash = hash(?1) AND url = ?1"
);
stmt.bindByIndex(0, url);
try {
if (!stmt.executeStep()) {
throw new Error("No result for frecency.");
}
return stmt.getInt32(0);
} finally {
@@ -561,17 +561,17 @@ function frecencyForUrl(aURI)
* @param aURI
* The URI or spec to get hidden for.
* @return @return true if the url is hidden, false otherwise.
*/
function isUrlHidden(aURI)
{
let url = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
let stmt = DBConn().createStatement(
- "SELECT hidden FROM moz_places WHERE url = ?1"
+ "SELECT hidden FROM moz_places WHERE url_hash = hash(?1) AND url = ?1"
);
stmt.bindByIndex(0, url);
if (!stmt.executeStep())
throw new Error("No result for hidden.");
let hidden = stmt.getInt32(0);
stmt.finalize();
return !!hidden;
@@ -638,17 +638,17 @@ function do_get_guid_for_uri(aURI,
aStack)
{
if (!aStack) {
aStack = Components.stack.caller;
}
let stmt = DBConn().createStatement(
`SELECT guid
FROM moz_places
- WHERE url = :url`
+ WHERE url_hash = hash(:url) AND url = :url`
);
stmt.params.url = aURI.spec;
do_check_true(stmt.executeStep(), aStack);
let guid = stmt.row.guid;
stmt.finalize();
do_check_valid_places_guid(guid, aStack);
return guid;
}
@@ -861,12 +861,12 @@ function checkBookmarkObject(info) {
* Reads foreign_count value for a given url.
*/
function* foreign_count(url) {
if (url instanceof Ci.nsIURI)
url = url.spec;
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.executeCached(
`SELECT foreign_count FROM moz_places
- WHERE url = :url
+ WHERE url_hash = hash(:url) AND url = :url
`, { url });
return rows.length == 0 ? 0 : rows[0].getResultByName("foreign_count");
}
new file mode 100644
index 0000000000000000000000000000000000000000..6071dc6a68566d834ae751a0b649e3ae79c33140
GIT binary patch
literal 1146880
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
z00000000000000000000000000001h|6i?5m+cxld+*Hf^0KmUxMXly@uF<DGjl|3
zZFhI~i1yUVWLsN&L@K?oHqkMB-kjRjWIEBBT3*|o>}W}KXNHfgtL;v-HK*DWwMTbl
z+nX|}u5@$asNQ137o>BA66r(pS?!6IWLNukvzk*K*+fS+pO?v|yE=~TDH9tSJE^Ci
z*vPsOu~~_9Yoa+D%cd44vWpVw5wYK{xg<8dXN(KesS)wEw&coG`-uF|OBaNS>xYfH
z_kfKp)9Pm*+qiOB>%#hup?mE0+<sS2F0Bfk5Zbk@tSnR(8eCq|*%oh39FhOm=8qb4
zXeO1;9yM~*q3!YIvlE%Fwru98T<0f^o|l<4sdeUzS&N5<La&aRcicn6jw-1NP1vTH
zLj7@DRiD||+MGFl=B$QkD;x6F$NcKX&z)3U6`B_so~s@%%2j`liH|Qxb!BU}zRKdz
zy0(e)CpFZ~n!KWJ!iu4LeD<|TO{YeyLQQ{n!}+;SCl_>O6Wg>N4Xr!=l(8M-PFXQ$
z-smMK4BeykXY0>wj#P!(|L)fN)=P6&CYx$cuH3ehNN8PGcEa#v^NI1+&Pg+e?s0f|
z@dc+BRfU@W;Z7DM+B&yszbLdWIljBOy=m6ircCRyp?gG*zwPU1gsVcyLi^?a$VKGV
zXgc1uO&6hcqo<A=zIe`xW75l3o|5a~OFuiJGPk+H!Jk689{>OV000000000000000
z00000000000000000000c%RrUTpB71mz0;4g^KHkjk@=Mjg|TEnw>l>0000000000
z00000000000000000000000000002k$tv=PH9L6_0000000000000000000000000
z000000000000026lU3$^!@+Z*+z$W%00000000000000000000000000000000000
z0K6aU87>JOTvQfnPpwS0wZ%uI(hEbMtjJ}AgO@_N9{>OV00000000000000000000
z000000000000000c>k!199-U>TA6HXi;qa97gpv&XT22)9tyq?oDtNmer@$PR|l&n
ztd6d_ch#k<PG5CcE)xI%00000000000000000000000000000000000_*Zv$)qqeq
z6fVzwEGZcrDGC+W53Aq!{QSQ2gb7_IFRY(BrE&J`#OTTaq4IFJ?BGJ#(xTak=0ry}
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
ziz@T=*X-o+0{{R30000000000000000000000000000000002MPF9)wjRZ%9f=hx$
zxf1{Y000000000000000000000000000000000000Q}=>BD;jb;n1M6aJZzTvS@aq
zInj}w8()}-Mnc8)!|FHu`^LWW;YEd_2N#MCD4L$iWKT?HlG#K{Z`seD7VA4N4i5;0
z%R_5>3P&ciw8R#qQcK$7=_Q$1HWkY*N@ilMskW9xI-krW61h;KJ=T)UbhgD;BwAvr
zj(j9GaZ|0isZ`s7c)GW*?twS-ohx&fQMKxnQ1DQ2eb5~o9~`p!?bQ#izH;?~)pe^w
ztL|NOX)Y50000000000000000000000000000000000000O0?~5mnLJ>DBG2l}(G1
znQSV(Vp;y*d(UhWXzFZ>Hz!)+*+jCVC9!;DEcahSWwiE~J^zlnt?5*IU$uq-e`lrs
zeT~>9df1Ws<jZXOC+4?EG$pf%-X2E9>SOhF716_vez)?S@pPgi+gGC@Hu8w_=wWl-
ztB1CDCflBBNwy{vE&Y0HD2pD}xc9r%UD%auY3k}oF6~P6RU0{^G&<<W!aNjaHnaIt
zQ)_%#vN_dJC^{}yKe!}1X!Q0=E=pyxy~RcyS{xlTcKgK&GuoW$>d5BG)kLF%j@m)T
z=|pp)qj^PdlMN#x(LqzTUo<}zg=RP1rd-p*ilT##*?#%0r(j%cWKDR71L;b)H7$x~
z7WEaa$gR|};OC*>t>DkW%fTOm-v>_wzsv;y000000000000000000000000000000
z00000007{hTyZ2EE-lL+i;MHeXf%I}MDoX?qDZ8?JQ@jwOY?`4id^}!pgR=28N3=i
z5!@SmJ-9mfWG(;z000000000000000000000000000000000000095@?G+gwUYJge
zh_|&RSEkxWba!`;DBE7JczeO<_JWb^1&j8K3@_iN&B(6d5v7G=NkwijWx=DN;IF~!
z!OOw(!Lz|r!Q;6A000000000000000000000000000000000000002|D~LwQ!)4J(
zS-3QRD9Im+^M`2u5Xm2kB9XH4id@mM;3uKrt>DkWi@_g)r-EMx59I;?0000000000
z0000000000000000000000000008h$t0Yns4y`NhIYoO;k)Bggaipj`mlp|#7nDUK
zW#Q8Np`;>Lt1NgR6#O-KJ$N~IK6o~GDtJ5>000000000000000000000000000000
z000000001he`W(BCE-wU{jgE@9<Z@28VQ9<^M{iBp*Vkt<`0qlp(qjwl~?4dgoBqu
zxgP)k000000000000000000000000000000000000C*p%jD}0f%R<HV!$#eEz{ZMP
zBpm!Tl=}ey000000000000000000000000000000000000D$+UNTjU1B6ki4uZD6z
z000000000000000000000000000000000000002+zOqZCq&!qyKWx;!2W+g!rK^JV
zq2SHnmEe!T)4}7x!@>Q*J;C>aZw0poHwV|}G64Vp0000000000000000000000000
z0000000000U?(eyhRegHm4#EufWoPGm%=GpQ8-1)3#X#8;%IrUNJ%sl4o9jAr=rrH
zQ*qBJ+H;EZoQjHbr}CnTTpv}zRiWUm;I-g|;Mw5G;FrOJ!M(xV!MB622e$;*=Q05R
z000000000000000000000000000000000000AS}TjfTVF(#S47=c0;|Xt+FFTAn+V
zg-go{sYpp76&X+{Q&d!1h!#~9qD7S@(eiLOQd~F{MT?{5xf;1+E-M->D=V+aby5|K
z4FxX;zYBg5{4n@tupziAxF|R~$OJ9Hyx`beCIA2c0000000000000000000000000
z0000000000Y+qG0ToNt~71s}|-|*c1m4#$UA$iH<MFR@S;zDxyqfhKoNJe{-SDaB%
zNJe^+^WP{hB#R2k?q@cZRYuE8!ab>Lr<PPkL%peu&(E*gB2_dXmn!c|ZO&TNerIHJ
zYWeN!OE;&MjTu<HDK+xid)7xQau-t;TpJ2r3!V#}2p$UV3BD72H5UK?0000000000
z00000000000000000000000000D%AB42YD3L!nnk%{%U)VWoYEF~7R;b0rm#;_%Qt
zKKt6FrsAHDtv_3TX0+$y;pN2_M0!5%_1u0}mz76~%ZKg}`O?ph=m|!SzwPTq6}jfi
zf)%0Q#o#x=Pl9g;Uk<JaF31G{00000000000000000000000000000000000007|s
zh20{9!(G`4!;{S?##=il&5Yy&Z4>8DYN(qvc}3lX6~*~La(s7ld(*73O_|nZMfpI>
zwEEe{Hm+ROy0E^ZEFU;&^t{ZZNv$(y%vwCWG#{AR*xH;qe&(!(X)7B_@`2+|8QU@L
zlofO4jb3s>bl1q>^3hYr4PQKG#WCq+D^ID&4W%mBGZefKJRaN^d@HyqxI9=FtOynb
zCk4j_qk}_onE(I)00000000000000000000000000000000000ydUir8657Obz){>
zDw&+MWYKX0@`0|7)?{0vr75pCAIN4}=Qh^WEm?BH@+FmpKq}R?Af7Iih!g_xg_)*a
z(R?5?e$v8Ob5e`bb#3EIc8v@!Pp4AZz7qKY?TL=AzI0JOa7uRWsJV;AEpA)BsJ%2F
zIB{Z2cEW-g>B))dt-Is{6DD+>ys&=il*ZY!6IJ=Z_~v-xq?48&+c<md<nnxA?9}9n
zwo`_u7SF98Uy%>gch=9Jv3SXtl`Sm`$|~|#yk;lQH~;_u00000000000000000000
z000000000000000>|_=B!<wBu2mk;800000000000000000000000000000000000
z*vTq#zmeb%q2SHn<=hDX000000000000000000000000000000000000092Ql}5wi
TaAi@Xa2`-pR2&VLmq-3LURoSz
--- a/toolkit/components/places/tests/migration/xpcshell.ini
+++ b/toolkit/components/places/tests/migration/xpcshell.ini
@@ -14,16 +14,17 @@ support-files =
places_v24.sqlite
places_v25.sqlite
places_v26.sqlite
places_v27.sqlite
places_v28.sqlite
places_v30.sqlite
places_v31.sqlite
places_v32.sqlite
+ places_v33.sqlite
[test_current_from_downgraded.js]
[test_current_from_v6.js]
[test_current_from_v11.js]
[test_current_from_v19.js]
[test_current_from_v24.js]
[test_current_from_v25.js]
[test_current_from_v26.js]
--- a/toolkit/components/places/tests/queries/head_queries.js
+++ b/toolkit/components/places/tests/queries/head_queries.js
@@ -55,17 +55,17 @@ function* task_populateDB(aArray)
visitDate: qdata.lastVisit,
referrer: qdata.referrer ? uri(qdata.referrer) : null,
title: qdata.title
});
if (qdata.visitCount && !qdata.isDetails) {
// Set a fake visit_count, this is not a real count but can be used
// to test sorting by visit_count.
let stmt = DBConn().createAsyncStatement(
- "UPDATE moz_places SET visit_count = :vc WHERE url = :url");
+ "UPDATE moz_places SET visit_count = :vc WHERE url_hash = hash(:url) AND url = :url");
stmt.params.vc = qdata.visitCount;
stmt.params.url = qdata.uri;
try {
stmt.executeAsync();
}
catch (ex) {
print("Error while setting visit_count.");
}
@@ -74,17 +74,17 @@ function* task_populateDB(aArray)
}
}
}
if (qdata.isRedirect) {
// This must be async to properly enqueue after the updateFrecency call
// done by the visit addition.
let stmt = DBConn().createAsyncStatement(
- "UPDATE moz_places SET hidden = 1 WHERE url = :url");
+ "UPDATE moz_places SET hidden = 1 WHERE url_hash = hash(:url) AND url = :url");
stmt.params.url = qdata.uri;
try {
stmt.executeAsync();
}
catch (ex) {
print("Error while setting hidden.");
}
finally {
--- a/toolkit/components/places/tests/unit/test_async_history_api.js
+++ b/toolkit/components/places/tests/unit/test_async_history_api.js
@@ -1,11 +1,8 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
/**
* This file tests the async history API exposed by mozIAsyncHistory.
*/
////////////////////////////////////////////////////////////////////////////////
//// Globals
const TEST_DOMAIN = "http://mozilla.org/";
@@ -126,17 +123,17 @@ VisitObserver.prototype = {
*/
function do_check_title_for_uri(aURI,
aTitle)
{
let stack = Components.stack.caller;
let stmt = DBConn().createStatement(
`SELECT title
FROM moz_places
- WHERE url = :url`
+ WHERE url_hash = hash(:url) AND url = :url`
);
stmt.params.url = aURI.spec;
do_check_true(stmt.executeStep(), stack);
do_check_eq(stmt.row.title, aTitle, stack);
stmt.finalize();
}
////////////////////////////////////////////////////////////////////////////////
@@ -543,17 +540,18 @@ add_task(function* test_old_referrer_ign
do_check_true(yield promiseIsURIVisited(place.uri));
// Though the visit will not contain the referrer, we must examine the
// database to be sure.
do_check_eq(placeInfo.visits[0].referrerURI, null);
let stmt = DBConn().createStatement(
`SELECT COUNT(1) AS count
FROM moz_historyvisits
- WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url)
+ JOIN moz_places h ON h.id = place_id
+ WHERE url_hash = hash(:page_url) AND url = :page_url
AND from_visit = 0`
);
stmt.params.page_url = place.uri.spec;
do_check_true(stmt.executeStep());
do_check_eq(stmt.row.count, 1);
stmt.finalize();
yield PlacesTestUtils.promiseAsyncUpdates();
@@ -735,45 +733,45 @@ add_task(function* test_properties_saved
const EXPECTED_COUNT = visit.transitionType == TRANSITION_EMBED ? 0 : 1;
// mozIVisitInfo::date
let stmt = DBConn().createStatement(
`SELECT COUNT(1) AS count
FROM moz_places h
JOIN moz_historyvisits v
ON h.id = v.place_id
- WHERE h.url = :page_url
+ WHERE h.url_hash = hash(:page_url) AND h.url = :page_url
AND v.visit_date = :visit_date`
);
stmt.params.page_url = uri.spec;
stmt.params.visit_date = visit.visitDate;
do_check_true(stmt.executeStep());
do_check_eq(stmt.row.count, EXPECTED_COUNT);
stmt.finalize();
// mozIVisitInfo::transitionType
stmt = DBConn().createStatement(
`SELECT COUNT(1) AS count
FROM moz_places h
JOIN moz_historyvisits v
ON h.id = v.place_id
- WHERE h.url = :page_url
+ WHERE h.url_hash = hash(:page_url) AND h.url = :page_url
AND v.visit_type = :transition_type`
);
stmt.params.page_url = uri.spec;
stmt.params.transition_type = visit.transitionType;
do_check_true(stmt.executeStep());
do_check_eq(stmt.row.count, EXPECTED_COUNT);
stmt.finalize();
// mozIPlaceInfo::title
stmt = DBConn().createStatement(
`SELECT COUNT(1) AS count
FROM moz_places h
- WHERE h.url = :page_url
+ WHERE h.url_hash = hash(:page_url) AND h.url = :page_url
AND h.title = :title`
);
stmt.params.page_url = uri.spec;
stmt.params.title = placeInfo.title;
do_check_true(stmt.executeStep());
do_check_eq(stmt.row.count, EXPECTED_COUNT);
stmt.finalize();
@@ -836,21 +834,23 @@ add_task(function* test_referrer_saved()
// We need to insert all of our visits before we can test conditions.
if (++resultCount == places.length) {
do_check_true(places[0].uri.equals(visit.referrerURI));
let stmt = DBConn().createStatement(
`SELECT COUNT(1) AS count
FROM moz_historyvisits
- WHERE place_id = (SELECT id FROM moz_places WHERE url = :page_url)
+ JOIN moz_places h ON h.id = place_id
+ WHERE url_hash = hash(:page_url) AND url = :page_url
AND from_visit = (
- SELECT id
- FROM moz_historyvisits
- WHERE place_id = (SELECT id FROM moz_places WHERE url = :referrer)
+ SELECT v.id
+ FROM moz_historyvisits v
+ JOIN moz_places h ON h.id = place_id
+ WHERE url_hash = hash(:referrer) AND url = :referrer
)`
);
stmt.params.page_url = uri.spec;
stmt.params.referrer = visit.referrerURI.spec;
do_check_true(stmt.executeStep());
do_check_eq(stmt.row.count, 1);
stmt.finalize();
@@ -1112,18 +1112,19 @@ add_task(function* test_typed_hidden_not
visits: [
new VisitInfo(TRANSITION_FRAMED_LINK)
]
},
];
yield promiseUpdatePlaces(places);
let db = yield PlacesUtils.promiseDBConnection();
- let rows = yield db.execute("SELECT hidden, typed FROM moz_places WHERE url = :url",
- { url: "http://mozilla.org/" });
+ let rows = yield db.execute(
+ "SELECT hidden, typed FROM moz_places WHERE url_hash = hash(:url) AND url = :url",
+ { url: "http://mozilla.org/" });
Assert.equal(rows[0].getResultByName("typed"), 1,
"The page should be marked as typed");
Assert.equal(rows[0].getResultByName("hidden"), 0,
"The page should be marked as not hidden");
});
function run_test()
{
--- a/toolkit/components/places/tests/unit/test_history_clear.js
+++ b/toolkit/components/places/tests/unit/test_history_clear.js
@@ -126,17 +126,18 @@ add_task(function* test_history_clear()
// Check that history tables are empty
stmt = mDBConn.createStatement(
"SELECT * FROM (SELECT id FROM moz_historyvisits LIMIT 1)");
do_check_false(stmt.executeStep());
stmt.finalize();
// Check that all moz_places entries except bookmarks and place: have been removed
stmt = mDBConn.createStatement(
- `SELECT h.id FROM moz_places h WHERE SUBSTR(h.url, 1, 6) <> 'place:'
+ `SELECT h.id FROM moz_places h WHERE
+ url_hash NOT BETWEEN hash('place', 'prefix_lo') AND hash('place', 'prefix_hi')
AND NOT EXISTS (SELECT id FROM moz_bookmarks WHERE fk = h.id) LIMIT 1`);
do_check_false(stmt.executeStep());
stmt.finalize();
// Check that we only have favicons for retained places
stmt = mDBConn.createStatement(
`SELECT f.id FROM moz_favicons f WHERE NOT EXISTS
(SELECT id FROM moz_places WHERE favicon_id = f.id) LIMIT 1`);
@@ -155,12 +156,14 @@ add_task(function* test_history_clear()
`SELECT i.place_id FROM moz_inputhistory i WHERE NOT EXISTS
(SELECT id FROM moz_places WHERE id = i.place_id) LIMIT 1`);
do_check_false(stmt.executeStep());
stmt.finalize();
// Check that place:uris have frecency 0
stmt = mDBConn.createStatement(
`SELECT h.id FROM moz_places h
- WHERE SUBSTR(h.url, 1, 6) = 'place:' AND h.frecency <> 0 LIMIT 1`);
+ WHERE url_hash BETWEEN hash('place', 'prefix_lo')
+ AND hash('place', 'prefix_hi')
+ AND h.frecency <> 0 LIMIT 1`);
do_check_false(stmt.executeStep());
stmt.finalize();
});
--- a/toolkit/components/places/tests/unit/test_hosts_triggers.js
+++ b/toolkit/components/places/tests/unit/test_hosts_triggers.js
@@ -13,17 +13,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
// change its uri, then remove it, and
// for each change check that moz_hosts has correctly been updated.
function isHostInMozPlaces(aURI)
{
let stmt = DBConn().createStatement(
`SELECT url
FROM moz_places
- WHERE url = :host`
+ WHERE url_hash = hash(:host) AND url = :host`
);
let result = false;
stmt.params.host = aURI.spec;
while(stmt.executeStep()) {
if (stmt.row.url == aURI.spec) {
result = true;
break;
}
--- a/toolkit/components/places/tests/unit/test_preventive_maintenance.js
+++ b/toolkit/components/places/tests/unit/test_preventive_maintenance.js
@@ -37,17 +37,17 @@ function cleanDatabase() {
mDBConn.executeSimpleSQL("DELETE FROM moz_inputhistory");
mDBConn.executeSimpleSQL("DELETE FROM moz_keywords");
mDBConn.executeSimpleSQL("DELETE FROM moz_favicons");
mDBConn.executeSimpleSQL("DELETE FROM moz_bookmarks WHERE id > " + defaultBookmarksMaxId);
}
function addPlace(aUrl, aFavicon) {
let stmt = mDBConn.createStatement(
- "INSERT INTO moz_places (url, favicon_id) VALUES (:url, :favicon)");
+ "INSERT INTO moz_places (url, url_hash, favicon_id) VALUES (:url, hash(:url), :favicon)");
stmt.params["url"] = aUrl || "http://www.mozilla.org";
stmt.params["favicon"] = aFavicon || null;
stmt.execute();
stmt.finalize();
return mDBConn.lastInsertRowID;
}
function addBookmark(aPlaceId, aType, aParent, aKeywordId, aFolderType, aTitle) {
@@ -1072,26 +1072,28 @@ tests.push({
tests.push({
name: "L.2",
desc: "Recalculate visit_count and last_visit_date",
setup: function* () {
function setVisitCount(aURL, aValue) {
let stmt = mDBConn.createStatement(
- "UPDATE moz_places SET visit_count = :count WHERE url = :url"
+ `UPDATE moz_places SET visit_count = :count WHERE url_hash = hash(:url)
+ AND url = :url`
);
stmt.params.count = aValue;
stmt.params.url = aURL;
stmt.execute();
stmt.finalize();
}
function setLastVisitDate(aURL, aValue) {
let stmt = mDBConn.createStatement(
- "UPDATE moz_places SET last_visit_date = :date WHERE url = :url"
+ `UPDATE moz_places SET last_visit_date = :date WHERE url_hash = hash(:url)
+ AND url = :url`
);
stmt.params.date = aValue;
stmt.params.url = aURL;
stmt.execute();
stmt.finalize();
}
let now = Date.now() * 1000;
@@ -1146,18 +1148,18 @@ tests.push({
});
//------------------------------------------------------------------------------
tests.push({
name: "L.3",
desc: "recalculate hidden for redirects.",
- setup: function() {
- PlacesTestUtils.addVisits([
+ *setup() {
+ yield PlacesTestUtils.addVisits([
{ uri: NetUtil.newURI("http://l3.moz.org/"),
transition: TRANSITION_TYPED },
{ uri: NetUtil.newURI("http://l3.moz.org/redirecting/"),
transition: TRANSITION_TYPED },
{ uri: NetUtil.newURI("http://l3.moz.org/redirecting2/"),
transition: TRANSITION_REDIRECT_TEMPORARY,
referrer: NetUtil.newURI("http://l3.moz.org/redirecting/") },
{ uri: NetUtil.newURI("http://l3.moz.org/target/"),
@@ -1193,16 +1195,71 @@ tests.push({
stmt.finalize();
});
}
});
//------------------------------------------------------------------------------
tests.push({
+ name: "L.4",
+ desc: "recalculate foreign_count.",
+
+ *setup() {
+ this._pageGuid = (yield PlacesUtils.history.insert({ url: "http://l4.moz.org/",
+ visits: [{ date: new Date() }] })).guid;
+ yield PlacesUtils.bookmarks.insert({ url: "http://l4.moz.org/",
+ parentGuid: PlacesUtils.bookmarks.unfiledGuid});
+ yield PlacesUtils.keywords.insert({ url: "http://l4.moz.org/", keyword: "kw" });
+ Assert.equal((yield this._getForeignCount()), 2);
+ },
+
+ *_getForeignCount() {
+ let db = yield PlacesUtils.promiseDBConnection();
+ let rows = yield db.execute(`SELECT foreign_count FROM moz_places
+ WHERE guid = :guid`, { guid: this._pageGuid });
+ return rows[0].getResultByName("foreign_count");
+ },
+
+ *check() {
+ Assert.equal((yield this._getForeignCount()), 2);
+ }
+});
+
+//------------------------------------------------------------------------------
+
+tests.push({
+ name: "L.5",
+ desc: "recalculate hashes when missing.",
+
+ *setup() {
+ this._pageGuid = (yield PlacesUtils.history.insert({ url: "http://l5.moz.org/",
+ visits: [{ date: new Date() }] })).guid;
+ Assert.ok((yield this._getHash()) > 0);
+ yield PlacesUtils.withConnectionWrapper("change url hash", Task.async(function* (db) {
+ yield db.execute(`UPDATE moz_places SET url_hash = 0`);
+ }));
+ Assert.equal((yield this._getHash()), 0);
+ },
+
+ *_getHash() {
+ let db = yield PlacesUtils.promiseDBConnection();
+ let rows = yield db.execute(`SELECT url_hash FROM moz_places
+ WHERE guid = :guid`, { guid: this._pageGuid });
+ return rows[0].getResultByName("url_hash");
+ },
+
+ *check() {
+ Assert.ok((yield this._getHash()) > 0);
+ }
+});
+
+//------------------------------------------------------------------------------
+
+tests.push({
name: "Z",
desc: "Sanity: Preventive maintenance does not touch valid items",
_uri1: uri("http://www1.mozilla.org"),
_uri2: uri("http://www2.mozilla.org"),
_folderId: null,
_bookmarkId: null,
_separatorId: null,
@@ -1256,29 +1313,18 @@ tests.push({
resolve();
});
});
})
});
//------------------------------------------------------------------------------
-// main
-function run_test()
-{
- run_next_test();
-}
-
add_task(function* test_preventive_maintenance()
{
- // Force initialization of the bookmarks hash. This test could cause
- // it to go out of sync due to direct queries on the database.
- yield PlacesTestUtils.addVisits(uri("http://force.bookmarks.hash"));
- do_check_false(bs.isBookmarked(uri("http://force.bookmarks.hash")));
-
// Get current bookmarks max ID for cleanup
let stmt = mDBConn.createStatement("SELECT MAX(id) FROM moz_bookmarks");
stmt.executeStep();
defaultBookmarksMaxId = stmt.getInt32(0);
stmt.finalize();
do_check_true(defaultBookmarksMaxId > 0);
for (let [, test] in Iterator(tests)) {
--- a/toolkit/modules/Sqlite.jsm
+++ b/toolkit/modules/Sqlite.jsm
@@ -926,19 +926,20 @@ function openConnection(options) {
log.warn(`Could not open connection to ${path}: ${status}`);
reject(new Error(`Could not open connection to ${path}: ${status}`));
return;
}
log.info("Connection opened");
try {
resolve(
new OpenedConnection(connection.QueryInterface(Ci.mozIStorageAsyncConnection),
- identifier, openedOptions));
+ identifier, openedOptions));
} catch (ex) {
log.warn("Could not open database", ex);
+ connection.asyncClose();
reject(ex);
}
});
});
}
/**
* Creates a clone of an existing and open Storage connection. The clone has
@@ -1007,16 +1008,17 @@ function cloneStorageConnection(options)
reject(new Error("Could not clone connection: " + status));
}
log.info("Connection cloned");
try {
let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
resolve(new OpenedConnection(conn, identifier, openedOptions));
} catch (ex) {
log.warn("Could not clone database", ex);
+ connection.asyncClose();
reject(ex);
}
});
});
}
/**
* Wraps an existing and open Storage connection with Sqlite.jsm API. The