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;