Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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,
/* aDatabaseFilePath */ u""_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