Bug 858674 - 'Tweak SQLite pragmas on mobile devices for space savings'. r=khuey.
authorBen Turner <bent.mozilla@gmail.com>
Mon, 08 Apr 2013 12:47:13 -0700
changeset 134861 e0553df9a3be076896632fed62a90859fbda0640
parent 134860 a56bbca40488d3819ee7959b5741be5db8214602
child 134862 0b5b5d9a8761e76c436cf8502cad4485232d5bdd
push idunknown
push userunknown
push dateunknown
reviewerskhuey
bugs858674
milestone23.0a1
Bug 858674 - 'Tweak SQLite pragmas on mobile devices for space savings'. r=khuey.
dom/indexedDB/IDBFactory.cpp
dom/indexedDB/IDBFactory.h
dom/indexedDB/OpenDatabaseHelper.cpp
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -295,31 +295,50 @@ IDBFactory::GetConnection(const nsAStrin
   nsCOMPtr<mozIStorageService> ss =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(ss, nullptr);
 
   nsCOMPtr<mozIStorageConnection> connection;
   rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection));
   NS_ENSURE_SUCCESS(rv, nullptr);
 
-  // Turn on foreign key constraints and recursive triggers.
-  // The "INSERT OR REPLACE" statement doesn't fire the update trigger,
-  // instead it fires only the insert trigger. This confuses the update
-  // refcount function. This behavior changes with enabled recursive triggers,
-  // so the statement fires the delete trigger first and then the insert
-  // trigger.
-  rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "PRAGMA foreign_keys = ON; "
-    "PRAGMA recursive_triggers = ON;"
-  ));
+  rv = SetDefaultPragmas(connection);
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   return connection.forget();
 }
 
+// static
+nsresult
+IDBFactory::SetDefaultPragmas(mozIStorageConnection* aConnection)
+{
+  NS_ASSERTION(aConnection, "Null connection!");
+
+  static const char query[] =
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+    // Switch the journaling mode to TRUNCATE to avoid changing the directory
+    // structure at the conclusion of every transaction for devices with slower
+    // file systems.
+    "PRAGMA journal_mode = TRUNCATE; "
+#endif
+    // We use foreign keys in lots of places.
+    "PRAGMA foreign_keys = ON; "
+    // The "INSERT OR REPLACE" statement doesn't fire the update trigger,
+    // instead it fires only the insert trigger. This confuses the update
+    // refcount function. This behavior changes with enabled recursive triggers,
+    // so the statement fires the delete trigger first and then the insert
+    // trigger.
+    "PRAGMA recursive_triggers = ON;";
+
+  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(query));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  return NS_OK;
+}
+
 inline
 bool
 IgnoreWhitespace(PRUnichar c)
 {
   return false;
 }
 
 // static
--- a/dom/indexedDB/IDBFactory.h
+++ b/dom/indexedDB/IDBFactory.h
@@ -76,16 +76,19 @@ public:
   static already_AddRefed<nsIFileURL>
   GetDatabaseFileURL(nsIFile* aDatabaseFile, const nsACString& aOrigin);
 
   static already_AddRefed<mozIStorageConnection>
   GetConnection(const nsAString& aDatabaseFilePath,
                 const nsACString& aOrigin);
 
   static nsresult
+  SetDefaultPragmas(mozIStorageConnection* aConnection);
+
+  static nsresult
   LoadDatabaseInformation(mozIStorageConnection* aConnection,
                           nsIAtom* aDatabaseId,
                           uint64_t* aVersion,
                           ObjectStoreInfoArray& aObjectStores);
 
   static nsresult
   SetDatabaseMetadata(DatabaseInfo* aDatabaseInfo,
                       uint64_t aVersion,
--- a/dom/indexedDB/OpenDatabaseHelper.cpp
+++ b/dom/indexedDB/OpenDatabaseHelper.cpp
@@ -31,17 +31,17 @@ USING_QUOTA_NAMESPACE
 namespace {
 
 // If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
 // schema version.
 MOZ_STATIC_ASSERT(JS_STRUCTURED_CLONE_VERSION == 1,
                   "Need to update the major schema version.");
 
 // Major schema version. Bump for almost everything.
-const uint32_t kMajorSchemaVersion = 12;
+const uint32_t kMajorSchemaVersion = 13;
 
 // Minor schema version. Should almost always be 0 (maybe bump on release
 // branches if we have to).
 const uint32_t kMinorSchemaVersion = 0;
 
 // The schema version we store in the SQLite database is a (signed) 32-bit
 // integer. The major version is left-shifted 4 bits so the max value is
 // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
@@ -1318,16 +1318,43 @@ UpgradeSchemaFrom11_0To12_0(mozIStorageC
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = aConnection->SetSchemaVersion(MakeSchemaVersion(12, 0));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
+nsresult
+UpgradeSchemaFrom12_0To13_0(mozIStorageConnection* aConnection,
+                            bool* aVacuumNeeded)
+{
+  nsresult rv;
+
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+  int32_t defaultPageSize;
+  rv = aConnection->GetDefaultPageSize(&defaultPageSize);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Enable auto_vacuum mode and update the page size to the platform default.
+  nsAutoCString upgradeQuery("PRAGMA auto_vacuum = FULL; PRAGMA page_size = ");
+  upgradeQuery.AppendInt(defaultPageSize);
+
+  rv = aConnection->ExecuteSimpleSQL(upgradeQuery);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  *aVacuumNeeded = true;
+#endif
+
+  rv = aConnection->SetSchemaVersion(MakeSchemaVersion(13, 0));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
 class VersionChangeEventsRunnable;
 
 class SetVersionHelper : public AsyncConnectionHelper,
                          public IDBTransactionListener,
                          public AcquireListener
 {
   friend class VersionChangeEventsRunnable;
 
@@ -1809,16 +1836,19 @@ OpenDatabaseHelper::CreateDatabaseConnec
       rv = aFMDirectory->Remove(true);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection));
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
+  rv = IDBFactory::SetDefaultPragmas(connection);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   rv = connection->EnableModule(NS_LITERAL_CSTRING("filesystem"));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Check to make sure that the database schema is correct.
   int32_t schemaVersion;
   rv = connection->GetSchemaVersion(&schemaVersion);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1831,16 +1861,27 @@ OpenDatabaseHelper::CreateDatabaseConnec
   if (schemaVersion > kSQLiteSchemaVersion) {
     NS_WARNING("Unable to open IndexedDB database, schema is too high!");
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   bool vacuumNeeded = false;
 
   if (schemaVersion != kSQLiteSchemaVersion) {
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
+    if (!schemaVersion) {
+      // Have to do this before opening a transaction.
+      rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+        // Turn on auto_vacuum mode to reclaim disk space on mobile devices.
+        "PRAGMA auto_vacuum = FULL; "
+      ));
+      NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+    }
+#endif
+
     mozStorageTransaction transaction(connection, false,
                                   mozIStorageConnection::TRANSACTION_IMMEDIATE);
 
     if (!schemaVersion) {
       // Brand new file, initialize our tables.
       rv = CreateTables(connection);
       NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1858,17 +1899,17 @@ OpenDatabaseHelper::CreateDatabaseConnec
       rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName);
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
       rv = stmt->Execute();
       NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
     }
     else  {
       // This logic needs to change next time we change the schema!
-      MOZ_STATIC_ASSERT(kSQLiteSchemaVersion == int32_t((12 << 4) + 0),
+      MOZ_STATIC_ASSERT(kSQLiteSchemaVersion == int32_t((13 << 4) + 0),
                         "Need upgrade code from schema version increase.");
 
       while (schemaVersion != kSQLiteSchemaVersion) {
         if (schemaVersion == 4) {
           rv = UpgradeSchemaFrom4To5(connection);
         }
         else if (schemaVersion == 5) {
           rv = UpgradeSchemaFrom5To6(connection);
@@ -1887,52 +1928,47 @@ OpenDatabaseHelper::CreateDatabaseConnec
           rv = UpgradeSchemaFrom9_0To10_0(connection);
         }
         else if (schemaVersion == MakeSchemaVersion(10, 0)) {
           rv = UpgradeSchemaFrom10_0To11_0(connection);
         }
         else if (schemaVersion == MakeSchemaVersion(11, 0)) {
           rv = UpgradeSchemaFrom11_0To12_0(connection);
         }
+        else if (schemaVersion == MakeSchemaVersion(12, 0)) {
+          rv = UpgradeSchemaFrom12_0To13_0(connection, &vacuumNeeded);
+        }
         else {
           NS_WARNING("Unable to open IndexedDB database, no upgrade path is "
                      "available!");
           return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
         }
         NS_ENSURE_SUCCESS(rv, rv);
 
         rv = connection->GetSchemaVersion(&schemaVersion);
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
       NS_ASSERTION(schemaVersion == kSQLiteSchemaVersion, "Huh?!");
     }
 
-    rv = transaction.Commit();    
+    rv = transaction.Commit();
     if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
       // mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE,
       // which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
       rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
     }
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   if (vacuumNeeded) {
-    rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-      "VACUUM;"
-    ));
+    rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;"));
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  // Turn on foreign key constraints.
-  rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-    "PRAGMA foreign_keys = ON;"
-  ));
-  NS_ENSURE_SUCCESS(rv, rv);
-
   connection.forget(aConnection);
   return NS_OK;
 }
 
 nsresult
 OpenDatabaseHelper::StartSetVersion()
 {
   NS_ASSERTION(mState == eSetVersionPending, "Why are we here?");
@@ -2464,16 +2500,31 @@ DeleteDatabaseHelper::DoDatabaseWork(moz
     if (privilege != Chrome) {
       QuotaManager* quotaManager = QuotaManager::Get();
       NS_ASSERTION(quotaManager, "Shouldn't be null!");
 
       quotaManager->DecreaseUsageForOrigin(mASCIIOrigin, fileSize);
     }
   }
 
+  nsCOMPtr<nsIFile> dbJournalFile;
+  rv = directory->Clone(getter_AddRefs(dbJournalFile));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  rv = dbJournalFile->Append(filename + NS_LITERAL_STRING(".sqlite-journal"));
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  rv = dbJournalFile->Exists(&exists);
+  NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+
+  if (exists) {
+    rv = dbJournalFile->Remove(false);
+    NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
+  }
+
   nsCOMPtr<nsIFile> fmDirectory;
   rv = directory->Clone(getter_AddRefs(fmDirectory));
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   rv = fmDirectory->Append(filename);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
 
   rv = fmDirectory->Exists(&exists);