Bug 1184607 P7.4 Infrastructure for running Cache schema migrations. r=ehsan
authorBen Kelly <ben@wanderview.com>
Mon, 31 Aug 2015 14:26:30 -0700
changeset 280145 4ab4edf1f7307452c3a773c00283e048bf40690b
parent 280144 95fed4c06038b5b6bce9f5e50d88ea51d9cd8e6e
child 280146 5f09a49ac1a95b600a4ff1a15ab4d6a611c12782
push idunknown
push userunknown
push dateunknown
reviewersehsan
bugs1184607
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1184607 P7.4 Infrastructure for running Cache schema migrations. r=ehsan
dom/cache/DBSchema.cpp
dom/cache/DBSchema.h
dom/cache/Manager.cpp
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -9,16 +9,17 @@
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/cache/CacheTypes.h"
 #include "mozilla/dom/cache/SavedTypes.h"
 #include "mozilla/dom/cache/Types.h"
 #include "mozilla/dom/cache/TypeUtils.h"
 #include "mozIStorageConnection.h"
 #include "mozIStorageStatement.h"
+#include "mozStorageHelper.h"
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 #include "nsCRT.h"
 #include "nsHttp.h"
 #include "nsICryptoHash.h"
 #include "nsNetCID.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/dom/HeadersBinding.h"
@@ -34,18 +35,18 @@ namespace db {
 const int32_t kFirstShippedSchemaVersion = 15;
 
 namespace {
 
 const int32_t kLatestSchemaVersion = 15;
 
 // ---------
 // The following constants define the SQL schema.  These are defined in the
-// same order the SQL should be executed in CreateSchema().  They are broken
-// out as constants for convenient use in validation and migration.
+// same order the SQL should be executed in CreateOrMigrateSchema().  They are
+// broken out as constants for convenient use in validation and migration.
 // ---------
 
 // The caches table is the single source of truth about what Cache
 // objects exist for the origin.  The contents of the Cache are stored
 // in the entries table that references back to caches.
 //
 // The caches table is also referenced from storage.  Rows in storage
 // represent named Cache objects.  There are cases, however, where
@@ -321,20 +322,21 @@ static nsresult ExtractId(mozIStorageSta
                           nsID* aIdOut);
 static nsresult CreateAndBindKeyStatement(mozIStorageConnection* aConn,
                                           const char* aQueryFormat,
                                           const nsAString& aKey,
                                           mozIStorageStatement** aStateOut);
 static nsresult HashCString(nsICryptoHash* aCrypto, const nsACString& aIn,
                             nsACString& aOut);
 nsresult Validate(mozIStorageConnection* aConn);
+nsresult Migrate(mozIStorageConnection* aConn);
 } // namespace
 
 nsresult
-CreateSchema(mozIStorageConnection* aConn)
+CreateOrMigrateSchema(mozIStorageConnection* aConn)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
 
   int32_t schemaVersion;
   nsresult rv = aConn->GetSchemaVersion(&schemaVersion);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
@@ -342,17 +344,32 @@ CreateSchema(mozIStorageConnection* aCon
     // We already have the correct schema version.  Validate it matches
     // our expected schema and then proceed.
     rv = Validate(aConn);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     return rv;
   }
 
-  if (!schemaVersion) {
+  mozStorageTransaction trans(aConn, false,
+                              mozIStorageConnection::TRANSACTION_IMMEDIATE);
+  bool needVacuum = false;
+
+  if (schemaVersion) {
+    // A schema exists, but its not the current version.  Attempt to
+    // migrate it to our new schema.
+    rv = Migrate(aConn);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+    // Migrations happen infrequently and reflect a chance in DB structure.
+    // This is a good time to rebuild the database.
+    needVacuum = true;
+
+  } else {
+    // There is no schema installed.  Create the database from scratch.
     rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableCaches));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableSecurityInfo));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexSecurityInfoHash));
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@@ -380,16 +397,25 @@ CreateSchema(mozIStorageConnection* aCon
 
     rv = aConn->GetSchemaVersion(&schemaVersion);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
   }
 
   rv = Validate(aConn);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
+  rv = trans.Commit();
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  if (needVacuum) {
+    // Unfortunately, this must be performed outside of the transaction.
+    aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM"));
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+  }
+
   return rv;
 }
 
 nsresult
 InitializeConnection(mozIStorageConnection* aConn)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
@@ -2312,14 +2338,73 @@ Validate(mozIStorageConnection* aConn)
       return NS_ERROR_FAILURE;
     }
   }
 #endif
 
   return rv;
 }
 
+// -----
+// Schema migration code
+// -----
+
+typedef nsresult (*MigrationFunc)(mozIStorageConnection*);
+struct Migration
+{
+  Migration(int32_t aFromVersion, MigrationFunc aFunc)
+    : mFromVersion(aFromVersion)
+    , mFunc(aFunc)
+  { }
+  int32_t mFromVersion;
+  MigrationFunc mFunc;
+};
+
+// Declare migration functions here.  Each function should upgrade
+// the version by a single increment.  Don't skip versions.
+
+// Configure migration functions to run for the given starting version.
+Migration sMigrationList[] = {
+};
+
+uint32_t sMigrationListLength = sizeof(sMigrationList) / sizeof(Migration);
+
+nsresult
+Migrate(mozIStorageConnection* aConn)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aConn);
+
+  int32_t currentVersion = 0;
+  nsresult rv = aConn->GetSchemaVersion(&currentVersion);
+  if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+  while (currentVersion < kLatestSchemaVersion) {
+    // Wiping old databases is handled in DBAction because it requires
+    // making a whole new mozIStorageConnection.  Make sure we don't
+    // accidentally get here for one of those old databases.
+    MOZ_ASSERT(currentVersion >= kFirstShippedSchemaVersion);
+
+    for (uint32_t i = 0; i < sMigrationListLength; ++i) {
+      if (sMigrationList[i].mFromVersion == currentVersion) {
+        rv = sMigrationList[i].mFunc(aConn);
+        if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+        break;
+      }
+    }
+
+    DebugOnly<int32_t> lastVersion = currentVersion;
+    rv = aConn->GetSchemaVersion(&currentVersion);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+    MOZ_ASSERT(currentVersion > lastVersion);
+  }
+
+  MOZ_ASSERT(currentVersion == kLatestSchemaVersion);
+
+  return rv;
+}
+
 } // anonymous namespace
 
 } // namespace db
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/cache/DBSchema.h
+++ b/dom/cache/DBSchema.h
@@ -24,18 +24,19 @@ class CacheQueryParams;
 class CacheRequest;
 class CacheRequestOrVoid;
 class CacheResponse;
 struct SavedRequest;
 struct SavedResponse;
 
 namespace db {
 
+// Note, this cannot be executed within a transaction.
 nsresult
-CreateSchema(mozIStorageConnection* aConn);
+CreateOrMigrateSchema(mozIStorageConnection* aConn);
 
 // Note, this cannot be executed within a transaction.
 nsresult
 InitializeConnection(mozIStorageConnection* aConn);
 
 nsresult
 CreateCacheId(mozIStorageConnection* aConn, CacheId* aCacheIdOut);
 
--- a/dom/cache/Manager.cpp
+++ b/dom/cache/Manager.cpp
@@ -49,26 +49,19 @@ public:
 
   virtual nsresult
   RunSyncWithDBOnTarget(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
                         mozIStorageConnection* aConn) override
   {
     nsresult rv = BodyCreateDir(aDBDir);
     if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-    {
-      mozStorageTransaction trans(aConn, false,
-                                  mozIStorageConnection::TRANSACTION_IMMEDIATE);
-
-      rv = db::CreateSchema(aConn);
-      if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-
-      rv = trans.Commit();
-      if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-    }
+    // executes in its own transaction
+    rv = db::CreateOrMigrateSchema(aConn);
+    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
     // If the Context marker file exists, then the last session was
     // not cleanly shutdown.  In these cases sqlite will ensure that
     // the database is valid, but we might still orphan data.  Both
     // Cache objects and body files can be referenced by DOM objects
     // after they are "removed" from their parent.  So we need to
     // look and see if any of these late access objects have been
     // orphaned.