Bug 675996 - Part 1: extend moz_favicons with GUID to support Sync. r=mak
authorRichard Newman <rnewman@mozilla.com>
Thu, 01 Dec 2011 13:58:19 -0800
changeset 81921 9d2dd9486d41828ae6c19478a9e228632d2a34ea
parent 81920 25081a39d0bb4b32219f5e3c0784619a37e7e70c
child 81922 8c47ef9419acbaa82366586f36ecfa95724ae444
push idunknown
push userunknown
push dateunknown
reviewersmak
bugs675996
milestone11.0a1
Bug 675996 - Part 1: extend moz_favicons with GUID to support Sync. r=mak
toolkit/components/places/AsyncFaviconHelpers.cpp
toolkit/components/places/AsyncFaviconHelpers.h
toolkit/components/places/Database.cpp
toolkit/components/places/Database.h
toolkit/components/places/nsFaviconService.cpp
toolkit/components/places/nsNavHistory.cpp
toolkit/components/places/nsPlacesIndexes.h
toolkit/components/places/nsPlacesTables.h
--- a/toolkit/components/places/AsyncFaviconHelpers.cpp
+++ b/toolkit/components/places/AsyncFaviconHelpers.cpp
@@ -16,16 +16,17 @@
  * The Original Code is Places.
  *
  * The Initial Developer of the Original Code is the Mozilla Foundation.
  * Portions created by the Initial Developer are Copyright (C) 2010
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Marco Bonardo <mak77@bonardo.net> (original author)
+ *   Richard Newman <rnewman@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -719,33 +720,50 @@ AsyncAssociateIconToPage::Run()
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   mozStorageTransaction transaction(mDB->MainConn(), false,
                                     mozIStorageConnection::TRANSACTION_IMMEDIATE);
 
   // If there is no entry for this icon, or the entry is obsolete, replace it.
   if (mIcon.id == 0 || (mIcon.status & ICON_STATUS_CHANGED)) {
+    // The 'multi-coalesce' here ensures that replacing a favicon without
+    // specifying a :guid parameter doesn't cause it to be allocated a new
+    // GUID.
     nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
       "INSERT OR REPLACE INTO moz_favicons "
-        "(id, url, data, mime_type, expiration) "
+        "(id, url, data, mime_type, expiration, guid) "
       "VALUES ((SELECT id FROM moz_favicons WHERE url = :icon_url), "
-              ":icon_url, :data, :mime_type, :expiration) "
+              ":icon_url, :data, :mime_type, :expiration, "
+              "COALESCE(:guid, "
+                       "(SELECT guid FROM moz_favicons "
+                        "WHERE url = :icon_url), "
+                       "GENERATE_GUID()))"
     );
     NS_ENSURE_STATE(stmt);
     mozStorageStatementScoper scoper(stmt);
     rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), mIcon.spec);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = stmt->BindBlobByName(NS_LITERAL_CSTRING("data"),
                               TO_INTBUFFER(mIcon.data), mIcon.data.Length());
     NS_ENSURE_SUCCESS(rv, rv);
     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("mime_type"), mIcon.mimeType);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("expiration"), mIcon.expiration);
     NS_ENSURE_SUCCESS(rv, rv);
+
+    // Binding a GUID allows us to override the current (or generated) GUID.
+    if (mIcon.guid.IsEmpty()) {
+      rv = stmt->BindNullByName(NS_LITERAL_CSTRING("guid"));
+    }
+    else {
+      rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), mIcon.guid);
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+
     rv = stmt->Execute();
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Get the new icon id.  Do this regardless mIcon.id, since other code
     // could have added a entry before us.  Indeed we interrupted the thread
     // after the previous call to FetchIconInfo.
     rv = FetchIconInfo(mDB, mIcon);
     NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/places/AsyncFaviconHelpers.h
+++ b/toolkit/components/places/AsyncFaviconHelpers.h
@@ -70,25 +70,27 @@ enum AsyncFaviconFetchMode {
 struct IconData
 {
   IconData()
   : id(0)
   , expiration(0)
   , fetchMode(FETCH_NEVER)
   , status(ICON_STATUS_UNKNOWN)
   {
+    guid.SetIsVoid(PR_TRUE);
   }
 
   PRInt64 id;
   nsCString spec;
   nsCString data;
   nsCString mimeType;
   PRTime expiration;
   enum AsyncFaviconFetchMode fetchMode;
   PRUint16 status; // This is a bitset, see ICON_STATUS_* defines above.
+  nsCString guid;
 };
 
 /**
  * Data cache for a page entry.
  */
 struct PageData
 {
   PageData()
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -28,16 +28,17 @@
  *   Asaf Romano <mano@mozilla.com>
  *   Marco Bonardo <mak77@bonardo.net>
  *   Edward Lee <edward.lee@engineering.uiuc.edu>
  *   Michael Ventnor <m.ventnor@gmail.com>
  *   Ehsan Akhgari <ehsan.akhgari@gmail.com>
  *   Drew Willcoxon <adw@mozilla.com>
  *   Philipp von Weitershausen <philipp@weitershausen.de>
  *   Paolo Amadini <http://www.amadzone.org/>
+ *   Richard Newman <rnewman@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -637,17 +638,23 @@ Database::InitSchema(bool* aDatabaseMigr
       // Firefox 4 uses schema version 11.
 
       // Firefox 8 uses schema version 12.
 
       if (currentSchemaVersion < 13) {
         rv = MigrateV13Up();
         NS_ENSURE_SUCCESS(rv, rv);
       }
-      // Firefox 11 uses schema version 13.
+
+      if (currentSchemaVersion < 14) {
+        rv = MigrateV14Up();
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
+      // Firefox 11 uses schema version 14.
 
       // Schema Upgrades must add migration code here.
     }
   }
   else {
     // This is a new database, so we have to create all the tables and indices.
 
     // moz_places.
@@ -945,17 +952,17 @@ Database::MigrateV7Up()
   NS_ENSURE_SUCCESS(rv, rv);
   if (!URLUniqueIndexExists) {
     return NS_ERROR_FILE_CORRUPTED;
   }
 
   mozStorageTransaction transaction(mMainConn, false);
 
   // We need an index on lastModified to catch quickly last modified bookmark
-  // title for tag container's children. This will be useful for sync too.
+  // title for tag container's children. This will be useful for Sync, too.
   bool lastModIndexExists = false;
   rv = mMainConn->IndexExists(
     NS_LITERAL_CSTRING("moz_bookmarks_itemlastmodifiedindex"),
     &lastModIndexExists);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!lastModIndexExists) {
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED);
@@ -1277,18 +1284,18 @@ Database::MigrateV11Up()
       "ALTER TABLE moz_bookmarks "
       "ADD COLUMN guid TEXT"
     ));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // moz_placess grew a guid column.  Add the column, but do not populate it
-    // with anything just yet.  We will do that soon.
+    // moz_places grew a guid column. Add the column, but do not populate it
+    // with anything just yet. We will do that soon.
     rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
       "ALTER TABLE moz_places "
       "ADD COLUMN guid TEXT"
     ));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -1301,35 +1308,65 @@ Database::MigrateV11Up()
   return NS_OK;
 }
 
 nsresult
 Database::MigrateV13Up()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  // Dynamic containers are no more supported.
-
-  // For existing profiles, we may not have a moz_bookmarks.guid column
+  // Dynamic containers are no longer supported.
   nsCOMPtr<mozIStorageAsyncStatement> deleteDynContainersStmt;
   nsresult rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
       "DELETE FROM moz_bookmarks WHERE type = :item_type"),
     getter_AddRefs(deleteDynContainersStmt));
   rv = deleteDynContainersStmt->BindInt32ByName(
     NS_LITERAL_CSTRING("item_type"),
     nsINavBookmarksService::TYPE_DYNAMIC_CONTAINER
   );
   NS_ENSURE_SUCCESS(rv, rv);
   nsCOMPtr<mozIStoragePendingStatement> ps;
   rv = deleteDynContainersStmt->ExecuteAsync(nsnull, getter_AddRefs(ps));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
+nsresult
+Database::MigrateV14Up()
+{
+  // For existing profiles, we may not have a moz_favicons.guid column.
+  // Add it here. We want it to be unique, but ALTER TABLE doesn't allow
+  // a uniqueness constraint, so the index must be created separately.
+  nsCOMPtr<mozIStorageStatement> hasGuidStatement;
+  nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
+      "SELECT guid FROM moz_favicons"),
+    getter_AddRefs(hasGuidStatement));
+
+  if (NS_FAILED(rv)) {
+    rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "ALTER TABLE moz_favicons "
+      "ADD COLUMN guid TEXT"
+    ));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Generate GUIDs for our existing favicons.
+    rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+      "UPDATE moz_favicons "
+      "SET guid = GENERATE_GUID()"
+    ));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // And now we can make the column unique.
+    rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_FAVICONS_GUID);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+  return NS_OK;
+}
+
 void
 Database::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mShuttingDown);
 
   mMainThreadStatements.FinalizeStatements();
   mMainThreadAsyncStatements.FinalizeStatements();
@@ -1407,16 +1444,20 @@ Database::Observe(nsISupports *aSubject,
       nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
         "SELECT 1 "
         "FROM moz_places "
         "WHERE guid IS NULL "
         "UNION ALL "
         "SELECT 1 "
         "FROM moz_bookmarks "
         "WHERE guid IS NULL "
+        "UNION ALL "
+        "SELECT 1 "
+        "FROM moz_favicons "
+        "WHERE guid IS NULL "
       ), getter_AddRefs(stmt));
       NS_ENSURE_SUCCESS(rv, rv);
 
       bool haveNullGuids;
       rv = stmt->ExecuteStep(&haveNullGuids);
       NS_ENSURE_SUCCESS(rv, rv);
       NS_ASSERTION(!haveNullGuids,
                    "Someone added an entry without adding a GUID!");
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -40,19 +40,19 @@
 
 #include "nsThreadUtils.h"
 #include "nsWeakReference.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIObserver.h"
 #include "mozilla/storage.h"
 #include "mozilla/storage/StatementCache.h"
 
-// This is the schema version, update it at any schema change and add a
+// This is the schema version. Update it at any schema change and add a
 // corresponding migrateVxx method below.
-#define DATABASE_SCHEMA_VERSION 13
+#define DATABASE_SCHEMA_VERSION 14
 
 // 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.
@@ -283,18 +283,20 @@ protected:
   /**
    * Helpers used by schema upgrades.
    */
   nsresult MigrateV7Up();
   nsresult MigrateV8Up();
   nsresult MigrateV9Up();
   nsresult MigrateV10Up();
   nsresult MigrateV11Up();
+  nsresult MigrateV13Up();
+  nsresult MigrateV14Up();
+
   nsresult CheckAndUpdateGUIDs();
-  nsresult MigrateV13Up();
 
 private:
   ~Database();
 
   /**
    * Singleton getter, invoked by class instantiation.
    *
    * Note: does AddRef.
--- a/toolkit/components/places/nsFaviconService.cpp
+++ b/toolkit/components/places/nsFaviconService.cpp
@@ -233,22 +233,25 @@ nsFaviconService::SetFaviconUrlForPage(n
     }
   }
 
   mozStorageTransaction transaction(mDB->MainConn(), false);
 
   if (iconId == -1) {
     // We did not find any entry for this icon, so create a new one.
     nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
-      "INSERT INTO moz_favicons (id, url, data, mime_type, expiration) "
-      "VALUES (:icon_id, :icon_url, :data, :mime_type, :expiration)"
+      "INSERT INTO moz_favicons (id, url, data, mime_type, expiration, guid) "
+      "VALUES (:icon_id, :icon_url, :data, :mime_type, :expiration, "
+              "COALESCE(:guid, GENERATE_GUID()))"
     );
     NS_ENSURE_STATE(stmt);
     mozStorageStatementScoper scoper(stmt);
 
+    rv = stmt->BindNullByName(NS_LITERAL_CSTRING("guid"));
+    NS_ENSURE_SUCCESS(rv, rv);
     rv = stmt->BindNullByName(NS_LITERAL_CSTRING("icon_id"));
     NS_ENSURE_SUCCESS(rv, rv);
     rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("icon_url"), aFaviconURI);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = stmt->BindNullByName(NS_LITERAL_CSTRING("data"));
     NS_ENSURE_SUCCESS(rv, rv);
     rv = stmt->BindNullByName(NS_LITERAL_CSTRING("mime_type"));
     NS_ENSURE_SUCCESS(rv, rv);
@@ -438,49 +441,56 @@ nsFaviconService::SetFaviconData(nsIURI*
     NS_ENSURE_SUCCESS(rv, rv);
 
     if (hasResult) {
       // Get id of the old entry and update it.
       PRInt64 id;
       rv = stmt->GetInt64(0, &id);
       NS_ENSURE_SUCCESS(rv, rv);
       statement = mDB->GetStatement(
-        "UPDATE moz_favicons SET data = :data, mime_type = :mime_type, "
-                                "expiration = :expiration "
+        "UPDATE moz_favicons SET "
+               "guid       = COALESCE(:guid, guid), "
+               "data       = :data, "
+               "mime_type  = :mime_type, "
+               "expiration = :expiration "
         "WHERE id = :icon_id"
       );
       NS_ENSURE_STATE(statement);
 
+      rv = statement->BindNullByName(NS_LITERAL_CSTRING("guid"));
+      NS_ENSURE_SUCCESS(rv, rv);
       rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("icon_id"), id);
       NS_ENSURE_SUCCESS(rv, rv);
       rv = statement->BindBlobByName(NS_LITERAL_CSTRING("data"), data, dataLen);
       NS_ENSURE_SUCCESS(rv, rv);
       rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("mime_type"), *mimeType);
       NS_ENSURE_SUCCESS(rv, rv);
       rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("expiration"), aExpiration);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     else {
       // Insert a new entry.
       statement = mDB->GetStatement(
-        "INSERT INTO moz_favicons (id, url, data, mime_type, expiration) "
-        "VALUES (:icon_id, :icon_url, :data, :mime_type, :expiration)"
-      );
+       "INSERT INTO moz_favicons (id, url, data, mime_type, expiration, guid) "
+       "VALUES (:icon_id, :icon_url, :data, :mime_type, :expiration, "
+               "COALESCE(:guid, GENERATE_GUID()))");
       NS_ENSURE_STATE(statement);
 
       rv = statement->BindNullByName(NS_LITERAL_CSTRING("icon_id"));
       NS_ENSURE_SUCCESS(rv, rv);
       rv = URIBinder::Bind(statement, NS_LITERAL_CSTRING("icon_url"), aFaviconURI);
       NS_ENSURE_SUCCESS(rv, rv);
       rv = statement->BindBlobByName(NS_LITERAL_CSTRING("data"), data, dataLen);
       NS_ENSURE_SUCCESS(rv, rv);
       rv = statement->BindUTF8StringByName(NS_LITERAL_CSTRING("mime_type"), *mimeType);
       NS_ENSURE_SUCCESS(rv, rv);
       rv = statement->BindInt64ByName(NS_LITERAL_CSTRING("expiration"), aExpiration);
       NS_ENSURE_SUCCESS(rv, rv);
+      rv = statement->BindNullByName(NS_LITERAL_CSTRING("guid"));
+      NS_ENSURE_SUCCESS(rv, rv);
     }
   }
   mozStorageStatementScoper statementScoper(statement);
 
   rv = statement->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -25,16 +25,17 @@
  *   Asaf Romano <mano@mozilla.com>
  *   Marco Bonardo <mak77@bonardo.net>
  *   Edward Lee <edward.lee@engineering.uiuc.edu>
  *   Michael Ventnor <m.ventnor@gmail.com>
  *   Ehsan Akhgari <ehsan.akhgari@gmail.com>
  *   Drew Willcoxon <adw@mozilla.com>
  *   Philipp von Weitershausen <philipp@weitershausen.de>
  *   Paolo Amadini <http://www.amadzone.org/>
+ *   Richard Newman <rnewman@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
--- a/toolkit/components/places/nsPlacesIndexes.h
+++ b/toolkit/components/places/nsPlacesIndexes.h
@@ -140,10 +140,18 @@
  * moz_items_annos
  */
 
 #define CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE \
   CREATE_PLACES_IDX( \
     "itemattributeindex", "moz_items_annos", "item_id, anno_attribute_id", "UNIQUE" \
   )
 
+/**
+ * moz_favicons
+ */
+
+#define CREATE_IDX_MOZ_FAVICONS_GUID \
+  CREATE_PLACES_IDX( \
+    "guid_uniqueindex", "moz_favicons", "guid", "UNIQUE" \
+  )
 
 #endif // nsPlacesIndexes_h__
--- a/toolkit/components/places/nsPlacesTables.h
+++ b/toolkit/components/places/nsPlacesTables.h
@@ -17,16 +17,17 @@
  *
  * The Initial Developer of the Original Code is
  * Mozilla Corporation.
  * Portions created by the Initial Developer are Copyright (C) 2008
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *   Shawn Wilsher <me@shawnwilsher.com> (Original Author)
+ *   Richard Newman <rnewman@mozilla.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either the GNU General Public License Version 2 or later (the "GPL"), or
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -122,16 +123,17 @@
 
 #define CREATE_MOZ_FAVICONS NS_LITERAL_CSTRING( \
   "CREATE TABLE moz_favicons (" \
     "  id INTEGER PRIMARY KEY" \
     ", url LONGVARCHAR UNIQUE" \
     ", data BLOB" \
     ", mime_type VARCHAR(32)" \
     ", expiration LONG" \
+    ", guid TEXT" \
   ")" \
 )
 
 #define CREATE_MOZ_BOOKMARKS NS_LITERAL_CSTRING( \
   "CREATE TABLE moz_bookmarks (" \
     "  id INTEGER PRIMARY KEY" \
     ", type INTEGER" \
     ", fk INTEGER DEFAULT NULL" /* place_id */ \