gecko-dev/dom/indexedDB/SchemaUpgrades.cpp
Cristian Tuns 3ea845929a Backed out 12 changesets (bug 934640) for causing wpt failures in idb-explicit-commit.any.html CLOSED TREE
Backed out changeset f842e5594224 (bug 934640)
Backed out changeset 3cf1e31d16ba (bug 934640)
Backed out changeset ba85838ef7cd (bug 934640)
Backed out changeset a2769ba4c0ab (bug 934640)
Backed out changeset 3a2af6a310ab (bug 934640)
Backed out changeset 8bbe6f8fb695 (bug 934640)
Backed out changeset 184282e69b59 (bug 934640)
Backed out changeset f3b863806999 (bug 934640)
Backed out changeset ed5bb2e976ef (bug 934640)
Backed out changeset f315683fd3ba (bug 934640)
Backed out changeset 30dbc38c3e9a (bug 934640)
Backed out changeset ef90771d141d (bug 934640)
2024-03-05 13:17:12 -05:00

3023 lines
84 KiB
C++

/* -*- 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{},
/* aDatabaseName */ u""_ns, /* aDatabaseID */ ""_ns,
/* aEnforcingQuota */ false,
/* aIsInPrivateBrowsingMode */ 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