summaryrefslogtreecommitdiffstats
path: root/dom/indexedDB/SchemaUpgrades.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/indexedDB/SchemaUpgrades.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/indexedDB/SchemaUpgrades.cpp')
-rw-r--r--dom/indexedDB/SchemaUpgrades.cpp3020
1 files changed, 3020 insertions, 0 deletions
diff --git a/dom/indexedDB/SchemaUpgrades.cpp b/dom/indexedDB/SchemaUpgrades.cpp
new file mode 100644
index 0000000000..6066390740
--- /dev/null
+++ b/dom/indexedDB/SchemaUpgrades.cpp
@@ -0,0 +1,3020 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "SchemaUpgrades.h"
+
+// local includes
+#include "ActorsParentCommon.h"
+#include "DatabaseFileInfo.h"
+#include "DatabaseFileManager.h"
+#include "DBSchema.h"
+#include "IndexedDatabase.h"
+#include "IndexedDatabaseInlines.h"
+#include "IndexedDBCommon.h"
+#include "ReportInternalError.h"
+
+// global includes
+#include <stdlib.h>
+#include <algorithm>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include "ErrorList.h"
+#include "MainThreadUtils.h"
+#include "SafeRefPtr.h"
+#include "js/RootingAPI.h"
+#include "js/StructuredClone.h"
+#include "js/Value.h"
+#include "jsapi.h"
+#include "mozIStorageConnection.h"
+#include "mozIStorageFunction.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageValueArray.h"
+#include "mozStorageHelper.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/MacroForEach.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/OriginAttributes.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/indexedDB/IDBResult.h"
+#include "mozilla/dom/indexedDB/Key.h"
+#include "mozilla/dom/quota/Assertions.h"
+#include "mozilla/dom/quota/PersistenceType.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "mozilla/fallible.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/mozalloc.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/storage/Variant.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsError.h"
+#include "nsISupports.h"
+#include "nsIVariant.h"
+#include "nsLiteralString.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsTLiteralString.h"
+#include "nsTStringRepr.h"
+#include "nsThreadUtils.h"
+#include "nscore.h"
+#include "snappy/snappy.h"
+
+struct JSContext;
+class JSObject;
+
+#if defined(MOZ_WIDGET_ANDROID)
+# define IDB_MOBILE
+#endif
+
+namespace mozilla::dom::indexedDB {
+
+using mozilla::ipc::IsOnBackgroundThread;
+using quota::AssertIsOnIOThread;
+using quota::PERSISTENCE_TYPE_INVALID;
+
+namespace {
+
+nsresult UpgradeSchemaFrom4To5(mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom4To5", DOM);
+
+ nsresult rv;
+
+ // All we changed is the type of the version column, so lets try to
+ // convert that to an integer, and if we fail, set it to 0.
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = aConnection.CreateStatement(
+ "SELECT name, version, dataVersion "
+ "FROM database"_ns,
+ getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsString name;
+ int32_t intVersion;
+ int64_t dataVersion;
+
+ {
+ mozStorageStatementScoper scoper(stmt);
+
+ QM_TRY_INSPECT(const bool& hasResults,
+ MOZ_TO_RESULT_INVOKE_MEMBER(stmt, ExecuteStep));
+
+ if (NS_WARN_IF(!hasResults)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsString version;
+ rv = stmt->GetString(1, version);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ intVersion = version.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ intVersion = 0;
+ }
+
+ rv = stmt->GetString(0, name);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->GetInt64(2, &dataVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE database"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE database ("
+ "name TEXT NOT NULL, "
+ "version INTEGER NOT NULL DEFAULT 0, "
+ "dataVersion INTEGER NOT NULL"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // The parameter names are not used, parameters are bound by index only
+ // locally in the same function.
+ rv = aConnection.CreateStatement(
+ "INSERT INTO database (name, version, dataVersion) "
+ "VALUES (:name, :version, :dataVersion)"_ns,
+ getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ {
+ mozStorageStatementScoper scoper(stmt);
+
+ rv = stmt->BindStringByIndex(0, name);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt32ByIndex(1, intVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindInt64ByIndex(2, dataVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ rv = aConnection.SetSchemaVersion(5);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom5To6(mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom5To6", DOM);
+
+ // First, drop all the indexes we're no longer going to use.
+ nsresult rv = aConnection.ExecuteSimpleSQL("DROP INDEX key_index;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP INDEX ai_key_index;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP INDEX value_index;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP INDEX ai_value_index;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Now, reorder the columns of object_data to put the blob data last. We do
+ // this by copying into a temporary table, dropping the original, then copying
+ // back into a newly created table.
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "id INTEGER PRIMARY KEY, "
+ "object_store_id, "
+ "key_value, "
+ "data "
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO temp_upgrade "
+ "SELECT id, object_store_id, key_value, data "
+ "FROM object_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE object_data ("
+ "id INTEGER PRIMARY KEY, "
+ "object_store_id INTEGER NOT NULL, "
+ "key_value DEFAULT NULL, "
+ "data BLOB NOT NULL, "
+ "UNIQUE (object_store_id, key_value), "
+ "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+ "CASCADE"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO object_data "
+ "SELECT id, object_store_id, key_value, data "
+ "FROM temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // We need to add a unique constraint to our ai_object_data table. Copy all
+ // the data out of it using a temporary table as before.
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "id INTEGER PRIMARY KEY, "
+ "object_store_id, "
+ "data "
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO temp_upgrade "
+ "SELECT id, object_store_id, data "
+ "FROM ai_object_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE ai_object_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE ai_object_data ("
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "object_store_id INTEGER NOT NULL, "
+ "data BLOB NOT NULL, "
+ "UNIQUE (object_store_id, id), "
+ "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+ "CASCADE"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO ai_object_data "
+ "SELECT id, object_store_id, data "
+ "FROM temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Fix up the index_data table. We're reordering the columns as well as
+ // changing the primary key from being a simple id to being a composite.
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "index_id, "
+ "value, "
+ "object_data_key, "
+ "object_data_id "
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO temp_upgrade "
+ "SELECT index_id, value, object_data_key, object_data_id "
+ "FROM index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE index_data ("
+ "index_id INTEGER NOT NULL, "
+ "value NOT NULL, "
+ "object_data_key NOT NULL, "
+ "object_data_id INTEGER NOT NULL, "
+ "PRIMARY KEY (index_id, value, object_data_key), "
+ "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+ "CASCADE, "
+ "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
+ "CASCADE"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT OR IGNORE INTO index_data "
+ "SELECT index_id, value, object_data_key, object_data_id "
+ "FROM temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE INDEX index_data_object_data_id_index "
+ "ON index_data (object_data_id);"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Fix up the unique_index_data table. We're reordering the columns as well as
+ // changing the primary key from being a simple id to being a composite.
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "index_id, "
+ "value, "
+ "object_data_key, "
+ "object_data_id "
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO temp_upgrade "
+ "SELECT index_id, value, object_data_key, object_data_id "
+ "FROM unique_index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE unique_index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE unique_index_data ("
+ "index_id INTEGER NOT NULL, "
+ "value NOT NULL, "
+ "object_data_key NOT NULL, "
+ "object_data_id INTEGER NOT NULL, "
+ "PRIMARY KEY (index_id, value, object_data_key), "
+ "UNIQUE (index_id, value), "
+ "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+ "CASCADE "
+ "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
+ "CASCADE"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO unique_index_data "
+ "SELECT index_id, value, object_data_key, object_data_id "
+ "FROM temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE INDEX unique_index_data_object_data_id_index "
+ "ON unique_index_data (object_data_id);"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Fix up the ai_index_data table. We're reordering the columns as well as
+ // changing the primary key from being a simple id to being a composite.
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "index_id, "
+ "value, "
+ "ai_object_data_id "
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO temp_upgrade "
+ "SELECT index_id, value, ai_object_data_id "
+ "FROM ai_index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE ai_index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE ai_index_data ("
+ "index_id INTEGER NOT NULL, "
+ "value NOT NULL, "
+ "ai_object_data_id INTEGER NOT NULL, "
+ "PRIMARY KEY (index_id, value, ai_object_data_id), "
+ "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+ "CASCADE, "
+ "FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE "
+ "CASCADE"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT OR IGNORE INTO ai_index_data "
+ "SELECT index_id, value, ai_object_data_id "
+ "FROM temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE INDEX ai_index_data_ai_object_data_id_index "
+ "ON ai_index_data (ai_object_data_id);"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Fix up the ai_unique_index_data table. We're reordering the columns as well
+ // as changing the primary key from being a simple id to being a composite.
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "index_id, "
+ "value, "
+ "ai_object_data_id "
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO temp_upgrade "
+ "SELECT index_id, value, ai_object_data_id "
+ "FROM ai_unique_index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE ai_unique_index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE ai_unique_index_data ("
+ "index_id INTEGER NOT NULL, "
+ "value NOT NULL, "
+ "ai_object_data_id INTEGER NOT NULL, "
+ "UNIQUE (index_id, value), "
+ "PRIMARY KEY (index_id, value, ai_object_data_id), "
+ "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+ "CASCADE, "
+ "FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE "
+ "CASCADE"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO ai_unique_index_data "
+ "SELECT index_id, value, ai_object_data_id "
+ "FROM temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE INDEX ai_unique_index_data_ai_object_data_id_index "
+ "ON ai_unique_index_data (ai_object_data_id);"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(6);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom6To7(mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom6To7", DOM);
+
+ nsresult rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "id, "
+ "name, "
+ "key_path, "
+ "auto_increment"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO temp_upgrade "
+ "SELECT id, name, key_path, auto_increment "
+ "FROM object_store;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_store;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE object_store ("
+ "id INTEGER PRIMARY KEY, "
+ "auto_increment INTEGER NOT NULL DEFAULT 0, "
+ "name TEXT NOT NULL, "
+ "key_path TEXT, "
+ "UNIQUE (name)"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO object_store "
+ "SELECT id, auto_increment, name, nullif(key_path, '') "
+ "FROM temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(7);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom7To8(mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom7To8", DOM);
+
+ nsresult rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "id, "
+ "object_store_id, "
+ "name, "
+ "key_path, "
+ "unique_index, "
+ "object_store_autoincrement"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO temp_upgrade "
+ "SELECT id, object_store_id, name, key_path, "
+ "unique_index, object_store_autoincrement "
+ "FROM object_store_index;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_store_index;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE object_store_index ("
+ "id INTEGER, "
+ "object_store_id INTEGER NOT NULL, "
+ "name TEXT NOT NULL, "
+ "key_path TEXT NOT NULL, "
+ "unique_index INTEGER NOT NULL, "
+ "multientry INTEGER NOT NULL, "
+ "object_store_autoincrement INTERGER NOT NULL, "
+ "PRIMARY KEY (id), "
+ "UNIQUE (object_store_id, name), "
+ "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+ "CASCADE"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO object_store_index "
+ "SELECT id, object_store_id, name, key_path, "
+ "unique_index, 0, object_store_autoincrement "
+ "FROM temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(8);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+class CompressDataBlobsFunction final : public mozIStorageFunction {
+ public:
+ NS_DECL_ISUPPORTS
+
+ private:
+ ~CompressDataBlobsFunction() = default;
+
+ NS_IMETHOD
+ OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** aResult) override {
+ MOZ_ASSERT(aArguments);
+ MOZ_ASSERT(aResult);
+
+ AUTO_PROFILER_LABEL("CompressDataBlobsFunction::OnFunctionCall", DOM);
+
+ uint32_t argc;
+ nsresult rv = aArguments->GetNumEntries(&argc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (argc != 1) {
+ NS_WARNING("Don't call me with the wrong number of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ int32_t type;
+ rv = aArguments->GetTypeOfIndex(0, &type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (type != mozIStorageStatement::VALUE_TYPE_BLOB) {
+ NS_WARNING("Don't call me with the wrong type of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ const uint8_t* uncompressed;
+ uint32_t uncompressedLength;
+ rv = aArguments->GetSharedBlob(0, &uncompressedLength, &uncompressed);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
+ UniqueFreePtr<uint8_t> compressed(
+ static_cast<uint8_t*>(malloc(compressedLength)));
+ if (NS_WARN_IF(!compressed)) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ snappy::RawCompress(
+ reinterpret_cast<const char*>(uncompressed), uncompressedLength,
+ reinterpret_cast<char*>(compressed.get()), &compressedLength);
+
+ std::pair<uint8_t*, int> data(compressed.release(), int(compressedLength));
+
+ nsCOMPtr<nsIVariant> result =
+ new mozilla::storage::AdoptedBlobVariant(data);
+
+ result.forget(aResult);
+ return NS_OK;
+ }
+};
+
+nsresult UpgradeSchemaFrom8To9_0(mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom8To9_0", DOM);
+
+ // We no longer use the dataVersion column.
+ nsresult rv =
+ aConnection.ExecuteSimpleSQL("UPDATE database SET dataVersion = 0;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ nsCOMPtr<mozIStorageFunction> compressor = new CompressDataBlobsFunction();
+
+ constexpr auto compressorName = "compress"_ns;
+
+ rv = aConnection.CreateFunction(compressorName, 1, compressor);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Turn off foreign key constraints before we do anything here.
+ rv = aConnection.ExecuteSimpleSQL(
+ "UPDATE object_data SET data = compress(data);"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "UPDATE ai_object_data SET data = compress(data);"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.RemoveFunction(compressorName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(MakeSchemaVersion(9, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom9_0To10_0(mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom9_0To10_0", DOM);
+
+ nsresult rv = aConnection.ExecuteSimpleSQL(
+ "ALTER TABLE object_data ADD COLUMN file_ids TEXT;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "ALTER TABLE ai_object_data ADD COLUMN file_ids TEXT;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = CreateFileTables(aConnection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(MakeSchemaVersion(10, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom10_0To11_0(mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom10_0To11_0", DOM);
+
+ nsresult rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "id, "
+ "object_store_id, "
+ "name, "
+ "key_path, "
+ "unique_index, "
+ "multientry"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO temp_upgrade "
+ "SELECT id, object_store_id, name, key_path, "
+ "unique_index, multientry "
+ "FROM object_store_index;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_store_index;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE object_store_index ("
+ "id INTEGER PRIMARY KEY, "
+ "object_store_id INTEGER NOT NULL, "
+ "name TEXT NOT NULL, "
+ "key_path TEXT NOT NULL, "
+ "unique_index INTEGER NOT NULL, "
+ "multientry INTEGER NOT NULL, "
+ "UNIQUE (object_store_id, name), "
+ "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+ "CASCADE"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO object_store_index "
+ "SELECT id, object_store_id, name, key_path, "
+ "unique_index, multientry "
+ "FROM temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "DROP TRIGGER object_data_insert_trigger;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO object_data (object_store_id, key_value, data, file_ids) "
+ "SELECT object_store_id, id, data, file_ids "
+ "FROM ai_object_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TRIGGER object_data_insert_trigger "
+ "AFTER INSERT ON object_data "
+ "FOR EACH ROW "
+ "WHEN NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(NULL, NEW.file_ids); "
+ "END;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO index_data (index_id, value, object_data_key, "
+ "object_data_id) "
+ "SELECT ai_index_data.index_id, ai_index_data.value, "
+ "ai_index_data.ai_object_data_id, object_data.id "
+ "FROM ai_index_data "
+ "INNER JOIN object_store_index ON "
+ "object_store_index.id = ai_index_data.index_id "
+ "INNER JOIN object_data ON "
+ "object_data.object_store_id = object_store_index.object_store_id AND "
+ "object_data.key_value = ai_index_data.ai_object_data_id;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO unique_index_data (index_id, value, object_data_key, "
+ "object_data_id) "
+ "SELECT ai_unique_index_data.index_id, ai_unique_index_data.value, "
+ "ai_unique_index_data.ai_object_data_id, object_data.id "
+ "FROM ai_unique_index_data "
+ "INNER JOIN object_store_index ON "
+ "object_store_index.id = ai_unique_index_data.index_id "
+ "INNER JOIN object_data ON "
+ "object_data.object_store_id = object_store_index.object_store_id AND "
+ "object_data.key_value = ai_unique_index_data.ai_object_data_id;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "UPDATE object_store "
+ "SET auto_increment = (SELECT max(id) FROM ai_object_data) + 1 "
+ "WHERE auto_increment;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE ai_unique_index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE ai_index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE ai_object_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(MakeSchemaVersion(11, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+class EncodeKeysFunction final : public mozIStorageFunction {
+ public:
+ NS_DECL_ISUPPORTS
+
+ private:
+ ~EncodeKeysFunction() = default;
+
+ NS_IMETHOD
+ OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** aResult) override {
+ MOZ_ASSERT(aArguments);
+ MOZ_ASSERT(aResult);
+
+ AUTO_PROFILER_LABEL("EncodeKeysFunction::OnFunctionCall", DOM);
+
+ uint32_t argc;
+ nsresult rv = aArguments->GetNumEntries(&argc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (argc != 1) {
+ NS_WARNING("Don't call me with the wrong number of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ int32_t type;
+ rv = aArguments->GetTypeOfIndex(0, &type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ QM_TRY_INSPECT(
+ const auto& key, ([type, aArguments]() -> Result<Key, nsresult> {
+ switch (type) {
+ case mozIStorageStatement::VALUE_TYPE_INTEGER: {
+ int64_t intKey;
+ aArguments->GetInt64(0, &intKey);
+
+ Key key;
+ QM_TRY(key.SetFromInteger(intKey));
+
+ return key;
+ }
+ case mozIStorageStatement::VALUE_TYPE_TEXT: {
+ nsString stringKey;
+ aArguments->GetString(0, stringKey);
+
+ Key key;
+ QM_TRY(key.SetFromString(stringKey));
+
+ return key;
+ }
+ default:
+ NS_WARNING("Don't call me with the wrong type of arguments!");
+ return Err(NS_ERROR_UNEXPECTED);
+ }
+ }()));
+
+ const nsCString& buffer = key.GetBuffer();
+
+ std::pair<const void*, int> data(static_cast<const void*>(buffer.get()),
+ int(buffer.Length()));
+
+ nsCOMPtr<nsIVariant> result = new mozilla::storage::BlobVariant(data);
+
+ result.forget(aResult);
+ return NS_OK;
+ }
+};
+
+nsresult UpgradeSchemaFrom11_0To12_0(mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom11_0To12_0", DOM);
+
+ constexpr auto encoderName = "encode"_ns;
+
+ nsCOMPtr<mozIStorageFunction> encoder = new EncodeKeysFunction();
+
+ nsresult rv = aConnection.CreateFunction(encoderName, 1, encoder);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "id INTEGER PRIMARY KEY, "
+ "object_store_id, "
+ "key_value, "
+ "data, "
+ "file_ids "
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO temp_upgrade "
+ "SELECT id, object_store_id, encode(key_value), data, file_ids "
+ "FROM object_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE object_data ("
+ "id INTEGER PRIMARY KEY, "
+ "object_store_id INTEGER NOT NULL, "
+ "key_value BLOB DEFAULT NULL, "
+ "file_ids TEXT, "
+ "data BLOB NOT NULL, "
+ "UNIQUE (object_store_id, key_value), "
+ "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
+ "CASCADE"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO object_data "
+ "SELECT id, object_store_id, key_value, file_ids, data "
+ "FROM temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TRIGGER object_data_insert_trigger "
+ "AFTER INSERT ON object_data "
+ "FOR EACH ROW "
+ "WHEN NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(NULL, NEW.file_ids); "
+ "END;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TRIGGER object_data_update_trigger "
+ "AFTER UPDATE OF file_ids ON object_data "
+ "FOR EACH ROW "
+ "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
+ "END;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TRIGGER object_data_delete_trigger "
+ "AFTER DELETE ON object_data "
+ "FOR EACH ROW WHEN OLD.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(OLD.file_ids, NULL); "
+ "END;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "index_id, "
+ "value, "
+ "object_data_key, "
+ "object_data_id "
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO temp_upgrade "
+ "SELECT index_id, encode(value), encode(object_data_key), object_data_id "
+ "FROM index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE index_data ("
+ "index_id INTEGER NOT NULL, "
+ "value BLOB NOT NULL, "
+ "object_data_key BLOB NOT NULL, "
+ "object_data_id INTEGER NOT NULL, "
+ "PRIMARY KEY (index_id, value, object_data_key), "
+ "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+ "CASCADE, "
+ "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
+ "CASCADE"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO index_data "
+ "SELECT index_id, value, object_data_key, object_data_id "
+ "FROM temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE INDEX index_data_object_data_id_index "
+ "ON index_data (object_data_id);"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TABLE temp_upgrade ("
+ "index_id, "
+ "value, "
+ "object_data_key, "
+ "object_data_id "
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO temp_upgrade "
+ "SELECT index_id, encode(value), encode(object_data_key), object_data_id "
+ "FROM unique_index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE unique_index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TABLE unique_index_data ("
+ "index_id INTEGER NOT NULL, "
+ "value BLOB NOT NULL, "
+ "object_data_key BLOB NOT NULL, "
+ "object_data_id INTEGER NOT NULL, "
+ "PRIMARY KEY (index_id, value, object_data_key), "
+ "UNIQUE (index_id, value), "
+ "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
+ "CASCADE "
+ "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
+ "CASCADE"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO unique_index_data "
+ "SELECT index_id, value, object_data_key, object_data_id "
+ "FROM temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE INDEX unique_index_data_object_data_id_index "
+ "ON unique_index_data (object_data_id);"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.RemoveFunction(encoderName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(MakeSchemaVersion(12, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom12_0To13_0(mozIStorageConnection& aConnection,
+ bool* aVacuumNeeded) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom12_0To13_0", DOM);
+
+ nsresult rv;
+
+#ifdef IDB_MOBILE
+ int32_t defaultPageSize;
+ rv = aConnection.GetDefaultPageSize(&defaultPageSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return 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);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ *aVacuumNeeded = true;
+#endif
+
+ rv = aConnection.SetSchemaVersion(MakeSchemaVersion(13, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom13_0To14_0(mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ // The only change between 13 and 14 was a different structured
+ // clone format, but it's backwards-compatible.
+ nsresult rv = aConnection.SetSchemaVersion(MakeSchemaVersion(14, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom14_0To15_0(mozIStorageConnection& aConnection) {
+ // The only change between 14 and 15 was a different structured
+ // clone format, but it's backwards-compatible.
+ nsresult rv = aConnection.SetSchemaVersion(MakeSchemaVersion(15, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom15_0To16_0(mozIStorageConnection& aConnection) {
+ // The only change between 15 and 16 was a different structured
+ // clone format, but it's backwards-compatible.
+ nsresult rv = aConnection.SetSchemaVersion(MakeSchemaVersion(16, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom16_0To17_0(mozIStorageConnection& aConnection) {
+ // The only change between 16 and 17 was a different structured
+ // clone format, but it's backwards-compatible.
+ nsresult rv = aConnection.SetSchemaVersion(MakeSchemaVersion(17, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+class UpgradeSchemaFrom17_0To18_0Helper final {
+ class InsertIndexDataValuesFunction;
+ class UpgradeKeyFunction;
+
+ public:
+ static nsresult DoUpgrade(mozIStorageConnection& aConnection,
+ const nsACString& aOrigin);
+
+ private:
+ static nsresult DoUpgradeInternal(mozIStorageConnection& aConnection,
+ const nsACString& aOrigin);
+
+ UpgradeSchemaFrom17_0To18_0Helper() = delete;
+ ~UpgradeSchemaFrom17_0To18_0Helper() = delete;
+};
+
+class UpgradeSchemaFrom17_0To18_0Helper::InsertIndexDataValuesFunction final
+ : public mozIStorageFunction {
+ public:
+ InsertIndexDataValuesFunction() = default;
+
+ NS_DECL_ISUPPORTS
+
+ private:
+ ~InsertIndexDataValuesFunction() = default;
+
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+NS_IMPL_ISUPPORTS(
+ UpgradeSchemaFrom17_0To18_0Helper::InsertIndexDataValuesFunction,
+ mozIStorageFunction);
+
+NS_IMETHODIMP
+UpgradeSchemaFrom17_0To18_0Helper::InsertIndexDataValuesFunction::
+ OnFunctionCall(mozIStorageValueArray* aValues, nsIVariant** _retval) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aValues);
+ MOZ_ASSERT(_retval);
+
+#ifdef DEBUG
+ {
+ uint32_t argCount;
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetNumEntries(&argCount));
+ MOZ_ASSERT(argCount == 4);
+
+ int32_t valueType;
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
+ valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(1, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
+
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(2, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
+
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(3, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+ }
+#endif
+
+ // Read out the previous value. It may be NULL, in which case we'll just end
+ // up with an empty array.
+ QM_TRY_UNWRAP(auto indexValues, ReadCompressedIndexDataValues(*aValues, 0));
+
+ IndexOrObjectStoreId indexId;
+ nsresult rv = aValues->GetInt64(1, &indexId);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int32_t unique;
+ rv = aValues->GetInt32(2, &unique);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ Key value;
+ rv = value.SetFromValueArray(aValues, 3);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the array with the new addition.
+ if (NS_WARN_IF(!indexValues.InsertElementSorted(
+ IndexDataValue(indexId, !!unique, value), fallible))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // Compress the array.
+ QM_TRY_UNWRAP((auto [indexValuesBlob, indexValuesBlobLength]),
+ MakeCompressedIndexDataValues(indexValues));
+
+ // The compressed blob is the result of this function.
+ nsCOMPtr<nsIVariant> result = new storage::AdoptedBlobVariant(
+ std::pair(indexValuesBlob.release(), indexValuesBlobLength));
+
+ result.forget(_retval);
+ return NS_OK;
+}
+
+class UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction final
+ : public mozIStorageFunction {
+ public:
+ UpgradeKeyFunction() = default;
+
+ static nsresult CopyAndUpgradeKeyBuffer(const uint8_t* aSource,
+ const uint8_t* aSourceEnd,
+ uint8_t* aDestination) {
+ return CopyAndUpgradeKeyBufferInternal(aSource, aSourceEnd, aDestination,
+ 0 /* aTagOffset */,
+ 0 /* aRecursionDepth */);
+ }
+
+ NS_DECL_ISUPPORTS
+
+ private:
+ ~UpgradeKeyFunction() = default;
+
+ static nsresult CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource,
+ const uint8_t* aSourceEnd,
+ uint8_t*& aDestination,
+ uint8_t aTagOffset,
+ uint8_t aRecursionDepth);
+
+ static uint32_t AdjustedSize(uint32_t aMaxSize, const uint8_t* aSource,
+ const uint8_t* aSourceEnd) {
+ MOZ_ASSERT(aMaxSize);
+ MOZ_ASSERT(aSource);
+ MOZ_ASSERT(aSourceEnd);
+ MOZ_ASSERT(aSource <= aSourceEnd);
+
+ return std::min(aMaxSize, uint32_t(aSourceEnd - aSource));
+ }
+
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+// static
+nsresult UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction::
+ CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource,
+ const uint8_t* aSourceEnd,
+ uint8_t*& aDestination, uint8_t aTagOffset,
+ uint8_t aRecursionDepth) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aSource);
+ MOZ_ASSERT(*aSource);
+ MOZ_ASSERT(aSourceEnd);
+ MOZ_ASSERT(aSource < aSourceEnd);
+ MOZ_ASSERT(aDestination);
+ MOZ_ASSERT(aTagOffset <= Key::kMaxArrayCollapse);
+
+ static constexpr uint8_t kOldNumberTag = 0x1;
+ static constexpr uint8_t kOldDateTag = 0x2;
+ static constexpr uint8_t kOldStringTag = 0x3;
+ static constexpr uint8_t kOldArrayTag = 0x4;
+ static constexpr uint8_t kOldMaxType = kOldArrayTag;
+
+ if (NS_WARN_IF(aRecursionDepth > Key::kMaxRecursionDepth)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ const uint8_t sourceTag = *aSource - (aTagOffset * kOldMaxType);
+ MOZ_ASSERT(sourceTag);
+
+ if (NS_WARN_IF(sourceTag > kOldMaxType * Key::kMaxArrayCollapse)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ if (sourceTag == kOldNumberTag || sourceTag == kOldDateTag) {
+ // Write the new tag.
+ *aDestination++ = (sourceTag == kOldNumberTag ? Key::eFloat : Key::eDate) +
+ (aTagOffset * Key::eMaxType);
+ aSource++;
+
+ // Numbers and Dates are encoded as 64-bit integers, but trailing 0
+ // bytes have been removed.
+ const uint32_t byteCount =
+ AdjustedSize(sizeof(uint64_t), aSource, aSourceEnd);
+
+ aDestination = std::copy(aSource, aSource + byteCount, aDestination);
+ aSource += byteCount;
+
+ return NS_OK;
+ }
+
+ if (sourceTag == kOldStringTag) {
+ // Write the new tag.
+ *aDestination++ = Key::eString + (aTagOffset * Key::eMaxType);
+ aSource++;
+
+ while (aSource < aSourceEnd) {
+ const uint8_t byte = *aSource++;
+ *aDestination++ = byte;
+
+ if (!byte) {
+ // Just copied the terminator.
+ break;
+ }
+
+ // Maybe copy one or two extra bytes if the byte is tagged and we have
+ // enough source space.
+ if (byte & 0x80) {
+ const uint32_t byteCount =
+ AdjustedSize((byte & 0x40) ? 2 : 1, aSource, aSourceEnd);
+
+ aDestination = std::copy(aSource, aSource + byteCount, aDestination);
+ aSource += byteCount;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(sourceTag < kOldArrayTag)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_FILE_CORRUPTED;
+ }
+
+ aTagOffset++;
+
+ if (aTagOffset == Key::kMaxArrayCollapse) {
+ MOZ_ASSERT(sourceTag == kOldArrayTag);
+
+ *aDestination++ = (aTagOffset * Key::eMaxType);
+ aSource++;
+
+ aTagOffset = 0;
+ }
+
+ while (aSource < aSourceEnd &&
+ (*aSource - (aTagOffset * kOldMaxType)) != Key::eTerminator) {
+ nsresult rv = CopyAndUpgradeKeyBufferInternal(
+ aSource, aSourceEnd, aDestination, aTagOffset, aRecursionDepth + 1);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ aTagOffset = 0;
+ }
+
+ if (aSource < aSourceEnd) {
+ MOZ_ASSERT((*aSource - (aTagOffset * kOldMaxType)) == Key::eTerminator);
+ *aDestination++ = Key::eTerminator + (aTagOffset * Key::eMaxType);
+ aSource++;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction,
+ mozIStorageFunction);
+
+NS_IMETHODIMP
+UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction::OnFunctionCall(
+ mozIStorageValueArray* aValues, nsIVariant** _retval) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+ MOZ_ASSERT(aValues);
+ MOZ_ASSERT(_retval);
+
+#ifdef DEBUG
+ {
+ uint32_t argCount;
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetNumEntries(&argCount));
+ MOZ_ASSERT(argCount == 1);
+
+ int32_t valueType;
+ MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType));
+ MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+ }
+#endif
+
+ // Dig the old key out of the values.
+ const uint8_t* blobData;
+ uint32_t blobDataLength;
+ nsresult rv = aValues->GetSharedBlob(0, &blobDataLength, &blobData);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Upgrading the key doesn't change the amount of space needed to hold it.
+ UniqueFreePtr<uint8_t> upgradedBlobData(
+ static_cast<uint8_t*>(malloc(blobDataLength)));
+ if (NS_WARN_IF(!upgradedBlobData)) {
+ IDB_REPORT_INTERNAL_ERR();
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = CopyAndUpgradeKeyBuffer(blobData, blobData + blobDataLength,
+ upgradedBlobData.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // The upgraded key is the result of this function.
+ std::pair<uint8_t*, int> data(upgradedBlobData.release(),
+ int(blobDataLength));
+
+ nsCOMPtr<nsIVariant> result = new mozilla::storage::AdoptedBlobVariant(data);
+
+ result.forget(_retval);
+ return NS_OK;
+}
+
+// static
+nsresult UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(
+ mozIStorageConnection& aConnection, const nsACString& aOrigin) {
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+
+ // Register the |upgrade_key| function.
+ RefPtr<UpgradeKeyFunction> updateFunction = new UpgradeKeyFunction();
+
+ constexpr auto upgradeKeyFunctionName = "upgrade_key"_ns;
+
+ nsresult rv =
+ aConnection.CreateFunction(upgradeKeyFunctionName, 1, updateFunction);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Register the |insert_idv| function.
+ RefPtr<InsertIndexDataValuesFunction> insertIDVFunction =
+ new InsertIndexDataValuesFunction();
+
+ constexpr auto insertIDVFunctionName = "insert_idv"_ns;
+
+ rv = aConnection.CreateFunction(insertIDVFunctionName, 4, insertIDVFunction);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_ALWAYS_SUCCEEDS(aConnection.RemoveFunction(upgradeKeyFunctionName));
+ return rv;
+ }
+
+ rv = DoUpgradeInternal(aConnection, aOrigin);
+
+ MOZ_ALWAYS_SUCCEEDS(aConnection.RemoveFunction(upgradeKeyFunctionName));
+ MOZ_ALWAYS_SUCCEEDS(aConnection.RemoveFunction(insertIDVFunctionName));
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+// static
+nsresult UpgradeSchemaFrom17_0To18_0Helper::DoUpgradeInternal(
+ mozIStorageConnection& aConnection, const nsACString& aOrigin) {
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+
+ nsresult rv = aConnection.ExecuteSimpleSQL(
+ // Drop these triggers to avoid unnecessary work during the upgrade
+ // process.
+ "DROP TRIGGER object_data_insert_trigger;"
+ "DROP TRIGGER object_data_update_trigger;"
+ "DROP TRIGGER object_data_delete_trigger;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // Drop these indexes before we do anything else to free disk space.
+ "DROP INDEX index_data_object_data_id_index;"
+ "DROP INDEX unique_index_data_object_data_id_index;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Create the new tables and triggers first.
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // This will eventually become the |database| table.
+ "CREATE TABLE database_upgrade "
+ "( name TEXT PRIMARY KEY"
+ ", origin TEXT NOT NULL"
+ ", version INTEGER NOT NULL DEFAULT 0"
+ ", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
+ ", last_analyze_time INTEGER NOT NULL DEFAULT 0"
+ ", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
+ ") WITHOUT ROWID;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // This will eventually become the |object_store| table.
+ "CREATE TABLE object_store_upgrade"
+ "( id INTEGER PRIMARY KEY"
+ ", auto_increment INTEGER NOT NULL DEFAULT 0"
+ ", name TEXT NOT NULL"
+ ", key_path TEXT"
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // This will eventually become the |object_store_index| table.
+ "CREATE TABLE object_store_index_upgrade"
+ "( id INTEGER PRIMARY KEY"
+ ", object_store_id INTEGER NOT NULL"
+ ", name TEXT NOT NULL"
+ ", key_path TEXT NOT NULL"
+ ", unique_index INTEGER NOT NULL"
+ ", multientry INTEGER NOT NULL"
+ ", FOREIGN KEY (object_store_id) "
+ "REFERENCES object_store(id) "
+ ");"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // This will eventually become the |object_data| table.
+ "CREATE TABLE object_data_upgrade"
+ "( object_store_id INTEGER NOT NULL"
+ ", key BLOB NOT NULL"
+ ", index_data_values BLOB DEFAULT NULL"
+ ", file_ids TEXT"
+ ", data BLOB NOT NULL"
+ ", PRIMARY KEY (object_store_id, key)"
+ ", FOREIGN KEY (object_store_id) "
+ "REFERENCES object_store(id) "
+ ") WITHOUT ROWID;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // This will eventually become the |index_data| table.
+ "CREATE TABLE index_data_upgrade"
+ "( index_id INTEGER NOT NULL"
+ ", value BLOB NOT NULL"
+ ", object_data_key BLOB NOT NULL"
+ ", object_store_id INTEGER NOT NULL"
+ ", PRIMARY KEY (index_id, value, object_data_key)"
+ ", FOREIGN KEY (index_id) "
+ "REFERENCES object_store_index(id) "
+ ", FOREIGN KEY (object_store_id, object_data_key) "
+ "REFERENCES object_data(object_store_id, key) "
+ ") WITHOUT ROWID;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // This will eventually become the |unique_index_data| table.
+ "CREATE TABLE unique_index_data_upgrade"
+ "( index_id INTEGER NOT NULL"
+ ", value BLOB NOT NULL"
+ ", object_store_id INTEGER NOT NULL"
+ ", object_data_key BLOB NOT NULL"
+ ", PRIMARY KEY (index_id, value)"
+ ", FOREIGN KEY (index_id) "
+ "REFERENCES object_store_index(id) "
+ ", FOREIGN KEY (object_store_id, object_data_key) "
+ "REFERENCES object_data(object_store_id, key) "
+ ") WITHOUT ROWID;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // Temporarily store |index_data_values| that we build during the upgrade
+ // of the index tables. We will later move this to the |object_data|
+ // table.
+ "CREATE TEMPORARY TABLE temp_index_data_values "
+ "( object_store_id INTEGER NOT NULL"
+ ", key BLOB NOT NULL"
+ ", index_data_values BLOB DEFAULT NULL"
+ ", PRIMARY KEY (object_store_id, key)"
+ ") WITHOUT ROWID;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // These two triggers help build the |index_data_values| blobs. The nested
+ // SELECT statements help us achieve an "INSERT OR UPDATE"-like behavior.
+ "CREATE TEMPORARY TRIGGER unique_index_data_upgrade_insert_trigger "
+ "AFTER INSERT ON unique_index_data_upgrade "
+ "BEGIN "
+ "INSERT OR REPLACE INTO temp_index_data_values "
+ "VALUES "
+ "( NEW.object_store_id"
+ ", NEW.object_data_key"
+ ", insert_idv("
+ "( SELECT index_data_values "
+ "FROM temp_index_data_values "
+ "WHERE object_store_id = NEW.object_store_id "
+ "AND key = NEW.object_data_key "
+ "), NEW.index_id"
+ ", 1" /* unique */
+ ", NEW.value"
+ ")"
+ ");"
+ "END;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TEMPORARY TRIGGER index_data_upgrade_insert_trigger "
+ "AFTER INSERT ON index_data_upgrade "
+ "BEGIN "
+ "INSERT OR REPLACE INTO temp_index_data_values "
+ "VALUES "
+ "( NEW.object_store_id"
+ ", NEW.object_data_key"
+ ", insert_idv("
+ "("
+ "SELECT index_data_values "
+ "FROM temp_index_data_values "
+ "WHERE object_store_id = NEW.object_store_id "
+ "AND key = NEW.object_data_key "
+ "), NEW.index_id"
+ ", 0" /* not unique */
+ ", NEW.value"
+ ")"
+ ");"
+ "END;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the |unique_index_data| table to change the column order, remove the
+ // ON DELETE CASCADE clauses, and to apply the WITHOUT ROWID optimization.
+ rv = aConnection.ExecuteSimpleSQL(
+ // Insert all the data.
+ "INSERT INTO unique_index_data_upgrade "
+ "SELECT "
+ "unique_index_data.index_id, "
+ "upgrade_key(unique_index_data.value), "
+ "object_data.object_store_id, "
+ "upgrade_key(unique_index_data.object_data_key) "
+ "FROM unique_index_data "
+ "JOIN object_data "
+ "ON unique_index_data.object_data_id = object_data.id;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // The trigger is no longer needed.
+ "DROP TRIGGER unique_index_data_upgrade_insert_trigger;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // The old table is no longer needed.
+ "DROP TABLE unique_index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // Rename the table.
+ "ALTER TABLE unique_index_data_upgrade "
+ "RENAME TO unique_index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the |index_data| table to change the column order, remove the ON
+ // DELETE CASCADE clauses, and to apply the WITHOUT ROWID optimization.
+ rv = aConnection.ExecuteSimpleSQL(
+ // Insert all the data.
+ "INSERT INTO index_data_upgrade "
+ "SELECT "
+ "index_data.index_id, "
+ "upgrade_key(index_data.value), "
+ "upgrade_key(index_data.object_data_key), "
+ "object_data.object_store_id "
+ "FROM index_data "
+ "JOIN object_data "
+ "ON index_data.object_data_id = object_data.id;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // The trigger is no longer needed.
+ "DROP TRIGGER index_data_upgrade_insert_trigger;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // The old table is no longer needed.
+ "DROP TABLE index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // Rename the table.
+ "ALTER TABLE index_data_upgrade "
+ "RENAME TO index_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the |object_data| table to add the |index_data_values| column,
+ // remove the ON DELETE CASCADE clause, and apply the WITHOUT ROWID
+ // optimization.
+ rv = aConnection.ExecuteSimpleSQL(
+ // Insert all the data.
+ "INSERT INTO object_data_upgrade "
+ "SELECT "
+ "object_data.object_store_id, "
+ "upgrade_key(object_data.key_value), "
+ "temp_index_data_values.index_data_values, "
+ "object_data.file_ids, "
+ "object_data.data "
+ "FROM object_data "
+ "LEFT JOIN temp_index_data_values "
+ "ON object_data.object_store_id = "
+ "temp_index_data_values.object_store_id "
+ "AND upgrade_key(object_data.key_value) = "
+ "temp_index_data_values.key;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // The temporary table is no longer needed.
+ "DROP TABLE temp_index_data_values;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // The old table is no longer needed.
+ "DROP TABLE object_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ // Rename the table.
+ "ALTER TABLE object_data_upgrade "
+ "RENAME TO object_data;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the |object_store_index| table to remove the UNIQUE constraint and
+ // the ON DELETE CASCADE clause.
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO object_store_index_upgrade "
+ "SELECT * "
+ "FROM object_store_index;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_store_index;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "ALTER TABLE object_store_index_upgrade "
+ "RENAME TO object_store_index;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the |object_store| table to remove the UNIQUE constraint.
+ rv = aConnection.ExecuteSimpleSQL(
+ "INSERT INTO object_store_upgrade "
+ "SELECT * "
+ "FROM object_store;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_store;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "ALTER TABLE object_store_upgrade "
+ "RENAME TO object_store;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Update the |database| table to include the origin, vacuum information, and
+ // apply the WITHOUT ROWID optimization.
+ nsCOMPtr<mozIStorageStatement> stmt;
+
+ // The parameter names are not used, parameters are bound by index only
+ // locally in the same function.
+ rv = aConnection.CreateStatement(
+ "INSERT INTO database_upgrade "
+ "SELECT name, :origin, version, 0, 0, 0 "
+ "FROM database;"_ns,
+ getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindUTF8StringByIndex(0, aOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL("DROP TABLE database;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "ALTER TABLE database_upgrade "
+ "RENAME TO database;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+#ifdef DEBUG
+ {
+ // Make sure there's only one entry in the |database| table.
+ QM_TRY_INSPECT(const auto& stmt,
+ quota::CreateAndExecuteSingleStepStatement(
+ aConnection, "SELECT COUNT(*) FROM database;"_ns),
+ QM_ASSERT_UNREACHABLE);
+
+ int64_t count;
+ MOZ_ASSERT(NS_SUCCEEDED(stmt->GetInt64(0, &count)));
+
+ MOZ_ASSERT(count == 1);
+ }
+#endif
+
+ // Recreate file table triggers.
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TRIGGER object_data_insert_trigger "
+ "AFTER INSERT ON object_data "
+ "WHEN NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(NULL, NEW.file_ids);"
+ "END;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TRIGGER object_data_update_trigger "
+ "AFTER UPDATE OF file_ids ON object_data "
+ "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(OLD.file_ids, NEW.file_ids);"
+ "END;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TRIGGER object_data_delete_trigger "
+ "AFTER DELETE ON object_data "
+ "WHEN OLD.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(OLD.file_ids, NULL);"
+ "END;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Finally, turn on auto_vacuum mode. We use full auto_vacuum mode to reclaim
+ // disk space on mobile devices (at the cost of some COMMIT speed), and
+ // incremental auto_vacuum mode on desktop builds.
+ rv = aConnection.ExecuteSimpleSQL(
+#ifdef IDB_MOBILE
+ "PRAGMA auto_vacuum = FULL;"_ns
+#else
+ "PRAGMA auto_vacuum = INCREMENTAL;"_ns
+#endif
+ );
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(MakeSchemaVersion(18, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom17_0To18_0(mozIStorageConnection& aConnection,
+ const nsACString& aOrigin) {
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom17_0To18_0", DOM);
+
+ return UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(aConnection, aOrigin);
+}
+
+nsresult UpgradeSchemaFrom18_0To19_0(mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ nsresult rv;
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom18_0To19_0", DOM);
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "ALTER TABLE object_store_index "
+ "ADD COLUMN locale TEXT;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "ALTER TABLE object_store_index "
+ "ADD COLUMN is_auto_locale BOOLEAN;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "ALTER TABLE index_data "
+ "ADD COLUMN value_locale BLOB;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "ALTER TABLE unique_index_data "
+ "ADD COLUMN value_locale BLOB;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE INDEX index_data_value_locale_index "
+ "ON index_data (index_id, value_locale, object_data_key, value) "
+ "WHERE value_locale IS NOT NULL;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE INDEX unique_index_data_value_locale_index "
+ "ON unique_index_data (index_id, value_locale, object_data_key, value) "
+ "WHERE value_locale IS NOT NULL;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(MakeSchemaVersion(19, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+class UpgradeFileIdsFunction final : public mozIStorageFunction {
+ SafeRefPtr<DatabaseFileManager> mFileManager;
+
+ public:
+ UpgradeFileIdsFunction() { AssertIsOnIOThread(); }
+
+ nsresult Init(nsIFile* aFMDirectory, mozIStorageConnection& aConnection);
+
+ NS_DECL_ISUPPORTS
+
+ private:
+ ~UpgradeFileIdsFunction() {
+ AssertIsOnIOThread();
+
+ if (mFileManager) {
+ mFileManager->Invalidate();
+ }
+ }
+
+ NS_IMETHOD
+ OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** aResult) override;
+};
+
+nsresult UpgradeSchemaFrom19_0To20_0(nsIFile* aFMDirectory,
+ mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom19_0To20_0", DOM);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aConnection.CreateStatement(
+ "SELECT count(*) "
+ "FROM object_data "
+ "WHERE file_ids IS NOT NULL"_ns,
+ getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ int64_t count;
+
+ {
+ mozStorageStatementScoper scoper(stmt);
+
+ QM_TRY_INSPECT(const bool& hasResult,
+ MOZ_TO_RESULT_INVOKE_MEMBER(stmt, ExecuteStep));
+
+ if (NS_WARN_IF(!hasResult)) {
+ MOZ_ASSERT(false, "This should never be possible!");
+ return NS_ERROR_FAILURE;
+ }
+
+ count = stmt->AsInt64(0);
+ if (NS_WARN_IF(count < 0)) {
+ MOZ_ASSERT(false, "This should never be possible!");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (count == 0) {
+ // Nothing to upgrade.
+ rv = aConnection.SetSchemaVersion(MakeSchemaVersion(20, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+ }
+
+ RefPtr<UpgradeFileIdsFunction> function = new UpgradeFileIdsFunction();
+
+ rv = function->Init(aFMDirectory, aConnection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ constexpr auto functionName = "upgrade"_ns;
+
+ rv = aConnection.CreateFunction(functionName, 2, function);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Disable update trigger.
+ rv = aConnection.ExecuteSimpleSQL(
+ "DROP TRIGGER object_data_update_trigger;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "UPDATE object_data "
+ "SET file_ids = upgrade(file_ids, data) "
+ "WHERE file_ids IS NOT NULL;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Enable update trigger.
+ rv = aConnection.ExecuteSimpleSQL(
+ "CREATE TRIGGER object_data_update_trigger "
+ "AFTER UPDATE OF file_ids ON object_data "
+ "FOR EACH ROW "
+ "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
+ "BEGIN "
+ "SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
+ "END;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.RemoveFunction(functionName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(MakeSchemaVersion(20, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+class UpgradeIndexDataValuesFunction final : public mozIStorageFunction {
+ public:
+ UpgradeIndexDataValuesFunction() { AssertIsOnIOThread(); }
+
+ NS_DECL_ISUPPORTS
+
+ private:
+ ~UpgradeIndexDataValuesFunction() { AssertIsOnIOThread(); }
+
+ using IndexDataValuesArray = IndexDataValuesAutoArray;
+ Result<IndexDataValuesArray, nsresult> ReadOldCompressedIDVFromBlob(
+ Span<const uint8_t> aBlobData);
+
+ NS_IMETHOD
+ OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** aResult) override;
+};
+
+NS_IMPL_ISUPPORTS(UpgradeIndexDataValuesFunction, mozIStorageFunction)
+
+Result<UpgradeIndexDataValuesFunction::IndexDataValuesArray, nsresult>
+UpgradeIndexDataValuesFunction::ReadOldCompressedIDVFromBlob(
+ const Span<const uint8_t> aBlobData) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!IsOnBackgroundThread());
+
+ IndexOrObjectStoreId indexId;
+ bool unique;
+ bool nextIndexIdAlreadyRead = false;
+
+ IndexDataValuesArray result;
+ for (auto remainder = aBlobData; !remainder.IsEmpty();) {
+ if (!nextIndexIdAlreadyRead) {
+ QM_TRY_UNWRAP((std::tie(indexId, unique, remainder)),
+ ReadCompressedIndexId(remainder));
+ }
+ nextIndexIdAlreadyRead = false;
+
+ if (NS_WARN_IF(remainder.IsEmpty())) {
+ IDB_REPORT_INTERNAL_ERR();
+ return Err(NS_ERROR_FILE_CORRUPTED);
+ }
+
+ // Read key buffer length.
+ QM_TRY_INSPECT(
+ (const auto& [keyBufferLength, remainderAfterKeyBufferLength]),
+ ReadCompressedNumber(remainder));
+
+ if (NS_WARN_IF(remainderAfterKeyBufferLength.IsEmpty()) ||
+ NS_WARN_IF(keyBufferLength > uint64_t(UINT32_MAX)) ||
+ NS_WARN_IF(keyBufferLength > remainderAfterKeyBufferLength.Length())) {
+ IDB_REPORT_INTERNAL_ERR();
+ return Err(NS_ERROR_FILE_CORRUPTED);
+ }
+
+ const auto [keyBuffer, remainderAfterKeyBuffer] =
+ remainderAfterKeyBufferLength.SplitAt(keyBufferLength);
+ if (NS_WARN_IF(!result.EmplaceBack(fallible, indexId, unique,
+ Key{nsCString{AsChars(keyBuffer)}}))) {
+ IDB_REPORT_INTERNAL_ERR();
+ return Err(NS_ERROR_OUT_OF_MEMORY);
+ }
+
+ remainder = remainderAfterKeyBuffer;
+ if (!remainder.IsEmpty()) {
+ // Read either a sort key buffer length or an index id.
+ QM_TRY_INSPECT((const auto& [maybeIndexId, remainderAfterIndexId]),
+ ReadCompressedNumber(remainder));
+
+ // Locale-aware indexes haven't been around long enough to have any users,
+ // we can safely assume all sort key buffer lengths will be zero.
+ // XXX This duplicates logic from ReadCompressedIndexId.
+ if (maybeIndexId != 0) {
+ unique = maybeIndexId % 2 == 1;
+ indexId = maybeIndexId / 2;
+ nextIndexIdAlreadyRead = true;
+ }
+
+ remainder = remainderAfterIndexId;
+ }
+ }
+ result.Sort();
+
+ return result;
+}
+
+NS_IMETHODIMP
+UpgradeIndexDataValuesFunction::OnFunctionCall(
+ mozIStorageValueArray* aArguments, nsIVariant** aResult) {
+ MOZ_ASSERT(aArguments);
+ MOZ_ASSERT(aResult);
+
+ AUTO_PROFILER_LABEL("UpgradeIndexDataValuesFunction::OnFunctionCall", DOM);
+
+ uint32_t argc;
+ nsresult rv = aArguments->GetNumEntries(&argc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (argc != 1) {
+ NS_WARNING("Don't call me with the wrong number of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ int32_t type;
+ rv = aArguments->GetTypeOfIndex(0, &type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (type != mozIStorageStatement::VALUE_TYPE_BLOB) {
+ NS_WARNING("Don't call me with the wrong type of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ const uint8_t* oldBlob;
+ uint32_t oldBlobLength;
+ rv = aArguments->GetSharedBlob(0, &oldBlobLength, &oldBlob);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ QM_TRY_INSPECT(const auto& oldIdv,
+ ReadOldCompressedIDVFromBlob(Span(oldBlob, oldBlobLength)));
+
+ QM_TRY_UNWRAP((auto [newIdv, newIdvLength]),
+ MakeCompressedIndexDataValues(oldIdv));
+
+ nsCOMPtr<nsIVariant> result = new storage::AdoptedBlobVariant(
+ std::pair(newIdv.release(), newIdvLength));
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom20_0To21_0(mozIStorageConnection& aConnection) {
+ // This should have been part of the 18 to 19 upgrade, where we changed the
+ // layout of the index_data_values blobs but didn't upgrade the existing data.
+ // See bug 1202788.
+
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom20_0To21_0", DOM);
+
+ RefPtr<UpgradeIndexDataValuesFunction> function =
+ new UpgradeIndexDataValuesFunction();
+
+ constexpr auto functionName = "upgrade_idv"_ns;
+
+ nsresult rv = aConnection.CreateFunction(functionName, 1, function);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "UPDATE object_data "
+ "SET index_data_values = upgrade_idv(index_data_values) "
+ "WHERE index_data_values IS NOT NULL;"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.RemoveFunction(functionName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(MakeSchemaVersion(21, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom21_0To22_0(mozIStorageConnection& aConnection) {
+ // The only change between 21 and 22 was a different structured clone format,
+ // but it's backwards-compatible.
+ nsresult rv = aConnection.SetSchemaVersion(MakeSchemaVersion(22, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom22_0To23_0(mozIStorageConnection& aConnection,
+ const nsACString& aOrigin) {
+ AssertIsOnIOThread();
+
+ MOZ_ASSERT(!aOrigin.IsEmpty());
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom22_0To23_0", DOM);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ // The parameter names are not used, parameters are bound by index only
+ // locally in the same function.
+ nsresult rv = aConnection.CreateStatement(
+ "UPDATE database SET origin = :origin;"_ns, getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->BindUTF8StringByIndex(0, aOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = stmt->Execute();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(MakeSchemaVersion(23, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom23_0To24_0(mozIStorageConnection& aConnection) {
+ // The only change between 23 and 24 was a different structured clone format,
+ // but it's backwards-compatible.
+ nsresult rv = aConnection.SetSchemaVersion(MakeSchemaVersion(24, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+nsresult UpgradeSchemaFrom24_0To25_0(mozIStorageConnection& aConnection) {
+ // The changes between 24 and 25 were an upgraded snappy library, a different
+ // structured clone format and a different file_ds format. But everything is
+ // backwards-compatible.
+ nsresult rv = aConnection.SetSchemaVersion(MakeSchemaVersion(25, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+class StripObsoleteOriginAttributesFunction final : public mozIStorageFunction {
+ public:
+ NS_DECL_ISUPPORTS
+
+ private:
+ ~StripObsoleteOriginAttributesFunction() = default;
+
+ NS_IMETHOD
+ OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** aResult) override {
+ MOZ_ASSERT(aArguments);
+ MOZ_ASSERT(aResult);
+
+ AUTO_PROFILER_LABEL("StripObsoleteOriginAttributesFunction::OnFunctionCall",
+ DOM);
+
+#ifdef DEBUG
+ {
+ uint32_t argCount;
+ MOZ_ALWAYS_SUCCEEDS(aArguments->GetNumEntries(&argCount));
+ MOZ_ASSERT(argCount == 1);
+
+ int32_t type;
+ MOZ_ALWAYS_SUCCEEDS(aArguments->GetTypeOfIndex(0, &type));
+ MOZ_ASSERT(type == mozIStorageValueArray::VALUE_TYPE_TEXT);
+ }
+#endif
+
+ nsCString origin;
+ nsresult rv = aArguments->GetUTF8String(0, origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ // Deserialize and re-serialize to automatically drop any obsolete origin
+ // attributes.
+ OriginAttributes oa;
+
+ nsCString originNoSuffix;
+ bool ok = oa.PopulateFromOrigin(origin, originNoSuffix);
+ if (NS_WARN_IF(!ok)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCString suffix;
+ oa.CreateSuffix(suffix);
+
+ nsCOMPtr<nsIVariant> result =
+ new mozilla::storage::UTF8TextVariant(originNoSuffix + suffix);
+
+ result.forget(aResult);
+ return NS_OK;
+ }
+};
+
+nsresult UpgradeSchemaFrom25_0To26_0(mozIStorageConnection& aConnection) {
+ AssertIsOnIOThread();
+
+ AUTO_PROFILER_LABEL("UpgradeSchemaFrom25_0To26_0", DOM);
+
+ constexpr auto functionName = "strip_obsolete_attributes"_ns;
+
+ nsCOMPtr<mozIStorageFunction> stripObsoleteAttributes =
+ new StripObsoleteOriginAttributesFunction();
+
+ nsresult rv = aConnection.CreateFunction(functionName,
+ /* aNumArguments */ 1,
+ stripObsoleteAttributes);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.ExecuteSimpleSQL(
+ "UPDATE DATABASE "
+ "SET origin = strip_obsolete_attributes(origin) "
+ "WHERE origin LIKE '%^%';"_ns);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.RemoveFunction(functionName);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ rv = aConnection.SetSchemaVersion(MakeSchemaVersion(26, 0));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(CompressDataBlobsFunction, mozIStorageFunction)
+NS_IMPL_ISUPPORTS(EncodeKeysFunction, mozIStorageFunction)
+NS_IMPL_ISUPPORTS(StripObsoleteOriginAttributesFunction, mozIStorageFunction);
+
+class DeserializeUpgradeValueHelper final : public Runnable {
+ public:
+ explicit DeserializeUpgradeValueHelper(
+ StructuredCloneReadInfoParent& aCloneReadInfo)
+ : Runnable("DeserializeUpgradeValueHelper"),
+ mMonitor("DeserializeUpgradeValueHelper::mMonitor"),
+ mCloneReadInfo(aCloneReadInfo),
+ mStatus(NS_ERROR_FAILURE) {}
+
+ nsresult DispatchAndWait(nsAString& aFileIds) {
+ // We don't need to go to the main-thread and use the sandbox.
+ if (!mCloneReadInfo.Data().Size()) {
+ PopulateFileIds(aFileIds);
+ return NS_OK;
+ }
+
+ // The operation will continue on the main-thread.
+
+ MOZ_ASSERT(!(mCloneReadInfo.Data().Size() % sizeof(uint64_t)));
+
+ MonitorAutoLock lock(mMonitor);
+
+ RefPtr<Runnable> self = this;
+ const nsresult rv = SchedulerGroup::Dispatch(self.forget());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ lock.Wait();
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ PopulateFileIds(aFileIds);
+ return NS_OK;
+ }
+
+ NS_IMETHOD
+ Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ JS::Rooted<JSObject*> global(cx, GetSandbox(cx));
+ if (NS_WARN_IF(!global)) {
+ OperationCompleted(NS_ERROR_FAILURE);
+ return NS_OK;
+ }
+
+ const JSAutoRealm ar(cx, global);
+
+ JS::Rooted<JS::Value> value(cx);
+ const nsresult rv = DeserializeUpgradeValue(cx, &value);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ OperationCompleted(rv);
+ return NS_OK;
+ }
+
+ OperationCompleted(NS_OK);
+ return NS_OK;
+ }
+
+ private:
+ nsresult DeserializeUpgradeValue(JSContext* aCx,
+ JS::MutableHandle<JS::Value> aValue) {
+ static const JSStructuredCloneCallbacks callbacks = {
+ StructuredCloneReadCallback<StructuredCloneReadInfoParent>,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr};
+
+ if (!JS_ReadStructuredClone(
+ aCx, mCloneReadInfo.Data(), JS_STRUCTURED_CLONE_VERSION,
+ JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue,
+ JS::CloneDataPolicy(), &callbacks, &mCloneReadInfo)) {
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+
+ return NS_OK;
+ }
+
+ void PopulateFileIds(nsAString& aFileIds) {
+ for (uint32_t count = mCloneReadInfo.Files().Length(), index = 0;
+ index < count; index++) {
+ const StructuredCloneFileParent& file = mCloneReadInfo.Files()[index];
+
+ const int64_t id = file.FileInfo().Id();
+
+ if (index) {
+ aFileIds.Append(' ');
+ }
+ aFileIds.AppendInt(file.Type() == StructuredCloneFileBase::eBlob ? id
+ : -id);
+ }
+ }
+
+ void OperationCompleted(nsresult aStatus) {
+ mStatus = aStatus;
+
+ MonitorAutoLock lock(mMonitor);
+ lock.Notify();
+ }
+
+ Monitor mMonitor MOZ_UNANNOTATED;
+ StructuredCloneReadInfoParent& mCloneReadInfo;
+ nsresult mStatus;
+};
+
+nsresult DeserializeUpgradeValueToFileIds(
+ StructuredCloneReadInfoParent& aCloneReadInfo, nsAString& aFileIds) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ const RefPtr<DeserializeUpgradeValueHelper> helper =
+ new DeserializeUpgradeValueHelper(aCloneReadInfo);
+ return helper->DispatchAndWait(aFileIds);
+}
+
+nsresult UpgradeFileIdsFunction::Init(nsIFile* aFMDirectory,
+ mozIStorageConnection& aConnection) {
+ // This DatabaseFileManager doesn't need real origin info, etc. The only
+ // purpose is to store file ids without adding more complexity or code
+ // duplication.
+ auto fileManager = MakeSafeRefPtr<DatabaseFileManager>(
+ PERSISTENCE_TYPE_INVALID, quota::OriginMetadata{}, u""_ns, ""_ns, false,
+ false);
+
+ nsresult rv = fileManager->Init(aFMDirectory, aConnection);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mFileManager = std::move(fileManager);
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(UpgradeFileIdsFunction, mozIStorageFunction)
+
+NS_IMETHODIMP
+UpgradeFileIdsFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
+ nsIVariant** aResult) {
+ MOZ_ASSERT(aArguments);
+ MOZ_ASSERT(aResult);
+ MOZ_ASSERT(mFileManager);
+
+ AUTO_PROFILER_LABEL("UpgradeFileIdsFunction::OnFunctionCall", DOM);
+
+ uint32_t argc;
+ nsresult rv = aArguments->GetNumEntries(&argc);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (argc != 2) {
+ NS_WARNING("Don't call me with the wrong number of arguments!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ QM_TRY_UNWRAP(auto cloneInfo, GetStructuredCloneReadInfoFromValueArray(
+ aArguments, 1, 0, *mFileManager));
+
+ nsAutoString fileIds;
+ // XXX does this really need non-const cloneInfo?
+ rv = DeserializeUpgradeValueToFileIds(cloneInfo, fileIds);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_DOM_DATA_CLONE_ERR;
+ }
+
+ nsCOMPtr<nsIVariant> result = new mozilla::storage::TextVariant(fileIds);
+
+ result.forget(aResult);
+ return NS_OK;
+}
+
+} // namespace
+
+Result<bool, nsresult> MaybeUpgradeSchema(mozIStorageConnection& aConnection,
+ const int32_t aSchemaVersion,
+ nsIFile& aFMDirectory,
+ const nsACString& aOrigin) {
+ bool vacuumNeeded = false;
+ int32_t schemaVersion = aSchemaVersion;
+
+ // This logic needs to change next time we change the schema!
+ static_assert(kSQLiteSchemaVersion == int32_t((26 << 4) + 0),
+ "Upgrade function needed due to schema version increase.");
+
+ while (schemaVersion != kSQLiteSchemaVersion) {
+ switch (schemaVersion) {
+ case 4:
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom4To5(aConnection)));
+ break;
+ case 5:
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom5To6(aConnection)));
+ break;
+ case 6:
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom6To7(aConnection)));
+ break;
+ case 7:
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom7To8(aConnection)));
+ break;
+ case 8:
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom8To9_0(aConnection)));
+ vacuumNeeded = true;
+ break;
+ case MakeSchemaVersion(9, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom9_0To10_0(aConnection)));
+ break;
+ case MakeSchemaVersion(10, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom10_0To11_0(aConnection)));
+ break;
+ case MakeSchemaVersion(11, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom11_0To12_0(aConnection)));
+ break;
+ case MakeSchemaVersion(12, 0):
+ QM_TRY(MOZ_TO_RESULT(
+ UpgradeSchemaFrom12_0To13_0(aConnection, &vacuumNeeded)));
+ break;
+ case MakeSchemaVersion(13, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom13_0To14_0(aConnection)));
+ break;
+ case MakeSchemaVersion(14, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom14_0To15_0(aConnection)));
+ break;
+ case MakeSchemaVersion(15, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom15_0To16_0(aConnection)));
+ break;
+ case MakeSchemaVersion(16, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom16_0To17_0(aConnection)));
+ break;
+ case MakeSchemaVersion(17, 0):
+ QM_TRY(
+ MOZ_TO_RESULT(UpgradeSchemaFrom17_0To18_0(aConnection, aOrigin)));
+ vacuumNeeded = true;
+ break;
+ case MakeSchemaVersion(18, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom18_0To19_0(aConnection)));
+ break;
+ case MakeSchemaVersion(19, 0):
+ QM_TRY(MOZ_TO_RESULT(
+ UpgradeSchemaFrom19_0To20_0(&aFMDirectory, aConnection)));
+ break;
+ case MakeSchemaVersion(20, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom20_0To21_0(aConnection)));
+ break;
+ case MakeSchemaVersion(21, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom21_0To22_0(aConnection)));
+ break;
+ case MakeSchemaVersion(22, 0):
+ QM_TRY(
+ MOZ_TO_RESULT(UpgradeSchemaFrom22_0To23_0(aConnection, aOrigin)));
+ break;
+ case MakeSchemaVersion(23, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom23_0To24_0(aConnection)));
+ break;
+ case MakeSchemaVersion(24, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom24_0To25_0(aConnection)));
+ break;
+ case MakeSchemaVersion(25, 0):
+ QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom25_0To26_0(aConnection)));
+ break;
+ default:
+ QM_FAIL(Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), []() {
+ IDB_WARNING(
+ "Unable to open IndexedDB database, no upgrade path is "
+ "available!");
+ });
+ }
+
+ QM_TRY_UNWRAP(schemaVersion,
+ MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
+ }
+
+ MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
+
+ return vacuumNeeded;
+}
+
+} // namespace mozilla::dom::indexedDB
+
+#undef IDB_MOBILE