Bug 889561 - moz_places_url_uniqueindex takes up too much database space draft
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 10 May 2016 11:53:56 +0200
changeset 376748 0bf0c1b4b93d37c9314c7a9ac1dd486a07c42c86
parent 376736 0907a56af1fd595f55cce12dd7127261033b54a6
child 523225 584ac212976d829c1f63bf08d8e17981fbe5b96e
push id20657
push usermak77@bonardo.net
push dateWed, 08 Jun 2016 16:20:51 +0000
bugs889561
milestone50.0a1
Bug 889561 - moz_places_url_uniqueindex takes up too much database space MozReview-Commit-ID: CpgvvFlupOP
browser/components/places/tests/browser/browser_markPageAsFollowedLink.js
browser/components/places/tests/unit/test_clearHistory_shutdown.js
services/sync/modules/engines/bookmarks.js
services/sync/modules/engines/history.js
services/sync/tests/unit/test_bookmark_invalid.js
services/sync/tests/unit/test_history_store.js
services/sync/tps/extensions/tps/resource/modules/history.jsm
toolkit/components/places/Bookmarks.jsm
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/FaviconHelpers.cpp
toolkit/components/places/History.cpp
toolkit/components/places/History.jsm
toolkit/components/places/PlacesDBUtils.jsm
toolkit/components/places/PlacesUtils.jsm
toolkit/components/places/SQLFunctions.cpp
toolkit/components/places/SQLFunctions.h
toolkit/components/places/UnifiedComplete.js
toolkit/components/places/nsAnnotationService.cpp
toolkit/components/places/nsNavBookmarks.cpp
toolkit/components/places/nsNavHistory.cpp
toolkit/components/places/nsNavHistoryResult.cpp
toolkit/components/places/nsPlacesAutoComplete.js
toolkit/components/places/nsPlacesExpiration.js
toolkit/components/places/nsPlacesIndexes.h
toolkit/components/places/nsPlacesTables.h
toolkit/components/places/nsTaggingService.js
toolkit/components/places/tests/PlacesTestUtils.jsm
toolkit/components/places/tests/bookmarks/test_1017502-bookmarks_foreign_count.js
toolkit/components/places/tests/browser/browser_bug248970.js
toolkit/components/places/tests/browser/browser_double_redirect.js
toolkit/components/places/tests/browser/browser_history_post.js
toolkit/components/places/tests/browser/browser_settitle.js
toolkit/components/places/tests/browser/browser_visituri.js
toolkit/components/places/tests/browser/head.js
toolkit/components/places/tests/cpp/places_test_harness.h
toolkit/components/places/tests/expiration/test_annos_expire_policy.js
toolkit/components/places/tests/expiration/test_clearHistory.js
toolkit/components/places/tests/expiration/test_outdated_analyze.js
toolkit/components/places/tests/head_common.js
toolkit/components/places/tests/migration/places_v33.sqlite
toolkit/components/places/tests/migration/test_current_from_v26.js
toolkit/components/places/tests/migration/test_current_from_v27.js
toolkit/components/places/tests/migration/test_current_from_v31.js
toolkit/components/places/tests/migration/xpcshell.ini
toolkit/components/places/tests/queries/head_queries.js
toolkit/components/places/tests/unit/test_async_history_api.js
toolkit/components/places/tests/unit/test_history_clear.js
toolkit/components/places/tests/unit/test_hosts_triggers.js
toolkit/components/places/tests/unit/test_preventive_maintenance.js
--- 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/browser/components/places/tests/unit/test_clearHistory_shutdown.js
+++ b/browser/components/places/tests/unit/test_clearHistory_shutdown.js
@@ -80,17 +80,17 @@ add_task(function* test_execute() {
   Assert.equal((yield getFormHistoryCount()), 1, "Added form history");
 
   do_print("Simulate and wait shutdown.");
   yield shutdownPlaces();
 
   Assert.equal((yield getFormHistoryCount()), 0, "Form history cleared");
 
   let stmt = DBConn(true).createStatement(
-    "SELECT id FROM moz_places WHERE url = :page_url "
+    "SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url "
   );
 
   try {
     URIS.forEach(function(aUrl) {
       stmt.params.page_url = aUrl;
       do_check_false(stmt.executeStep());
       stmt.reset();
     });
--- 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(${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);
 
@@ -684,17 +684,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 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);
@@ -2081,32 +2081,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,91 @@ 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 > 0, NS_ERROR_FAILURE);
+
+    nsString str;
+    aArguments->GetString(0, str);
+    nsAutoCString mode;
+    if (numEntries > 1) {
+      aArguments->GetUTF8String(1, mode);
+    }
+
+    nsCOMPtr<nsIWritableVariant> result =
+      do_CreateInstance("@mozilla.org/variant;1");
+    NS_ENSURE_STATE(result);
+
+    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;
+    }
+
+    NS_ADDREF(*_result = result);
+    return NS_OK;
+  }
+
 } // namespace places
 } // namespace mozilla
--- a/toolkit/components/places/SQLFunctions.h
+++ b/toolkit/components/places/SQLFunctions.h
@@ -66,18 +66,16 @@ public:
    * Registers the function with the specified database connection.
    *
    * @param aDBConn
    *        The database connection to register with.
    */
   static nsresult create(mozIStorageConnection *aDBConn);
 
 private:
-  ~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;
   static const uint32_t kArgIndexVisitCount = 4;
@@ -169,17 +167,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,17 +189,16 @@ 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
@@ -214,17 +210,16 @@ public:
 /**
  * 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
@@ -238,17 +233,16 @@ public:
  *
  * @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
@@ -267,17 +261,16 @@ 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
@@ -304,17 +297,16 @@ 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
@@ -333,17 +325,43 @@ 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);
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// 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
--- 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
@@ -883,17 +883,17 @@ nsPlacesExpiration.prototype = {
    * @param aCallback
    *        invoked on success, function (aPagesCount, aStatsCount).
    */
   _getPagesStats: function PEX__getPagesStats(aCallback) {
     if (!this._cachedStatements["LIMIT_COUNT"]) {
       this._cachedStatements["LIMIT_COUNT"] = this._db.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')`
       );
     }
     this._cachedStatements["LIMIT_COUNT"].executeAsync({
       _pagesCount: 0,
       _statsCount: 0,
       handleResult: function(aResults) {
         let row = aResults.getNextRow();
         this._pagesCount = row.getResultByIndex(0);
--- 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
@@ -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);
@@ -20,16 +15,17 @@ function promiseObserve(name, checkFn) {
 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) {
   let sql = `SELECT ${column}
              FROM ${table}
+             ${fromColumnName == "url" ? "url_hash = hash(:val)" : ""}
              WHERE ${fromColumnName} = :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];
   }
--- 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/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.hash_url = 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/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/test_current_from_v26.js
+++ b/toolkit/components/places/tests/migration/test_current_from_v26.js
@@ -2,20 +2,20 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 add_task(function* setup() {
   yield setupPlacesDatabase("places_v26.sqlite");
   // Setup database contents to be migrated.
   let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
   let db = yield Sqlite.openConnection({ path });
   // Add pages.
-  yield db.execute(`INSERT INTO moz_places (url, guid)
-                    VALUES ("http://test1.com/", "test1_______")
-                         , ("http://test2.com/", "test2_______")
-                         , ("http://test3.com/", "test3_______")
+  yield db.execute(`INSERT INTO moz_places (url, url_hash, guid)
+                    VALUES ("http://test1.com/", hash("http://test1.com/"), "test1_______")
+                         , ("http://test2.com/", hash("http://test2.com/"), "test2_______")
+                         , ("http://test3.com/", hash("http://test3.com/"), "test3_______")
                    `);
   // Add keywords.
   yield db.execute(`INSERT INTO moz_keywords (keyword)
                     VALUES ("kw1")
                          , ("kw2")
                          , ("kw3")
                          , ("kw4")
                          , ("kw5")
--- a/toolkit/components/places/tests/migration/test_current_from_v27.js
+++ b/toolkit/components/places/tests/migration/test_current_from_v27.js
@@ -2,19 +2,19 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 add_task(function* setup() {
   yield setupPlacesDatabase("places_v27.sqlite");
   // Setup database contents to be migrated.
   let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
   let db = yield Sqlite.openConnection({ path });
   // Add pages.
-  yield db.execute(`INSERT INTO moz_places (url, guid)
-                    VALUES ("http://test1.com/", "test1_______")
-                         , ("http://test2.com/", "test2_______")
+  yield db.execute(`INSERT INTO moz_places (url, url_hash, guid)
+                    VALUES ("http://test1.com/", hash("http://test1.com/"), "test1_______")
+                         , ("http://test2.com/", hash("http://test2.com/"), "test2_______")
                    `);
   // Add keywords.
   yield db.execute(`INSERT INTO moz_keywords (keyword, place_id, post_data)
                     VALUES ("kw1", (SELECT id FROM moz_places WHERE guid = "test2_______"), "broken data")
                          , ("kw2", (SELECT id FROM moz_places WHERE guid = "test2_______"), NULL)
                          , ("kw3", (SELECT id FROM moz_places WHERE guid = "test1_______"), "zzzzzzzzzz")
                    `);
   // Add bookmarks.
--- a/toolkit/components/places/tests/migration/test_current_from_v31.js
+++ b/toolkit/components/places/tests/migration/test_current_from_v31.js
@@ -4,20 +4,20 @@ let longurl = "http://example.com/" + "a
 let bmurl = "http://example.com/" + "a".repeat(1983);
 
 add_task(function* setup() {
   yield setupPlacesDatabase("places_v31.sqlite");
   // Setup database contents to be migrated.
   let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
   let db = yield Sqlite.openConnection({ path });
 
-  yield db.execute(`INSERT INTO moz_places (url, guid, foreign_count)
-                    VALUES (:shorturl, "test1_______", 0)
-                         , (:longurl, "test2_______", 0)
-                         , (:bmurl, "test3_______", 1)
+  yield db.execute(`INSERT INTO moz_places (url, url_hash, guid, foreign_count)
+                    VALUES (:shorturl, hash(:shorturl), "test1_______", 0)
+                         , (:longurl, hash(:longurl), "test2_______", 0)
+                         , (:bmurl, hash(:bmurl), "test3_______", 1)
                    `, { shorturl, longurl, bmurl });
   // Add visits.
   yield db.execute(`INSERT INTO moz_historyvisits (place_id)
                     VALUES ((SELECT id FROM moz_places WHERE url = :shorturl))
                          , ((SELECT id FROM moz_places WHERE url = :longurl))
                    `, { shorturl, longurl });
   yield db.close();
 });
--- 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)
+           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 < hash('place', 'prefix_lo') AND url_hash> 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,73 @@ tests.push({
       stmt.finalize();
     });
   }
 });
 
 //------------------------------------------------------------------------------
 
 tests.push({
+  name: "L.4",
+  desc: "recalculate foreign_count.",
+
+  _pageGuid,
+
+  *setup() {
+    this._pageGuid = (yield PlacesUtils.history.insert({ url: "http://l4.moz.org/" })).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.",
+
+  _pageGuid,
+
+  *setup() {
+    this._pageGuid = (yield PlacesUtils.history.insert({ url: "http://l4.moz.org/" })).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 +1315,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)) {