Source code

Revision control

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 "ActorsParent.h"
#include <algorithm>
#include <numeric>
#include <stdint.h> // UINTPTR_MAX, uintptr_t
#include <utility>
#include "FileManager.h"
#include "IDBCursorType.h"
#include "IDBObjectStore.h"
#include "IDBTransaction.h"
#include "IndexedDatabase.h"
#include "IndexedDatabaseInlines.h"
#include "IndexedDatabaseManager.h"
#include "js/StructuredClone.h"
#include "js/Value.h"
#include "jsapi.h"
#include "KeyPath.h"
#include "mozilla/ArrayAlgorithm.h"
#include "mozilla/Attributes.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/Casting.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/InitializedOnce.h"
#include "mozilla/JSObjectHolder.h"
#include "mozilla/LazyIdleThread.h"
#include "mozilla/Maybe.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/Preferences.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/SnappyCompressOutputStream.h"
#include "mozilla/SnappyUncompressInputStream.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/storage.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FileBlobImpl.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/dom/filehandle/ActorsParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseRequestParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBTransactionParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionParent.h"
#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsParent.h"
#include "mozilla/dom/IPCBlobUtils.h"
#include "mozilla/dom/quota/CheckedUnsafePtr.h"
#include "mozilla/dom/quota/Client.h"
#include "mozilla/dom/quota/FileStreams.h"
#include "mozilla/dom/quota/OriginScope.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/UsageInfo.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/InputStreamParams.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/ipc/PBackground.h"
#include "mozilla/ipc/PBackgroundParent.h"
#include "mozilla/RemoteLazyInputStreamParent.h"
#include "mozilla/Scoped.h"
#include "mozilla/storage/Variant.h"
#include "mozIStorageFunction.h"
#include "mozIStorageProgressHandler.h"
#include "mozIStorageService.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsClassHashtable.h"
#include "nsCOMPtr.h"
#include "nsDataHashtable.h"
#include "nsEscape.h"
#include "nsExceptionHandler.h"
#include "nsHashKeys.h"
#include "nsNetUtil.h"
#include "nsIAsyncInputStream.h"
#include "nsIEventTarget.h"
#include "nsIFile.h"
#include "nsIFileURL.h"
#include "nsIFileProtocolHandler.h"
#include "nsIInputStream.h"
#include "nsInterfaceHashtable.h"
#include "nsIOutputStream.h"
#include "nsIPrincipal.h"
#include "nsISupports.h"
#include "nsISupportsImpl.h"
#include "nsISupportsPriority.h"
#include "nsIThread.h"
#include "nsITimer.h"
#include "nsIURI.h"
#include "nsIURIMutator.h"
#include "nsIXPConnect.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsQueryObject.h"
#include "nsRefPtrHashtable.h"
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsStringStream.h"
#include "nsThreadPool.h"
#include "nsThreadUtils.h"
#include "nsXPCOMCID.h"
#include "PermissionRequestBase.h"
#include "ProfilerHelpers.h"
#include "prsystem.h"
#include "prtime.h"
#include "ReportInternalError.h"
#include "SafeRefPtr.h"
#include "snappy/snappy.h"
#define DISABLE_ASSERTS_FOR_FUZZING 0
#if DISABLE_ASSERTS_FOR_FUZZING
# define ASSERT_UNLESS_FUZZING(...) \
do { \
} while (0)
#else
# define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
#endif
#define IDB_DEBUG_LOG(_args) \
MOZ_LOG(IndexedDatabaseManager::GetLoggingModule(), LogLevel::Debug, _args)
#if defined(MOZ_WIDGET_ANDROID)
# define IDB_MOBILE
#endif
namespace mozilla {
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc,
PR_Close);
namespace dom::indexedDB {
using namespace mozilla::dom::quota;
using namespace mozilla::ipc;
using mozilla::dom::quota::Client;
namespace {
class ConnectionPool;
class Database;
struct DatabaseActorInfo;
class DatabaseFile;
class DatabaseLoggingInfo;
class DatabaseMaintenance;
class Factory;
class Maintenance;
class MutableFile;
class OpenDatabaseOp;
class TransactionBase;
class TransactionDatabaseOperationBase;
class VersionChangeTransaction;
template <bool StatementHasIndexKeyBindings>
struct ValuePopulateResponseHelper;
/*******************************************************************************
* Constants
******************************************************************************/
// If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
// schema version.
static_assert(JS_STRUCTURED_CLONE_VERSION == 8,
"Need to update the major schema version.");
// Major schema version. Bump for almost everything.
const uint32_t kMajorSchemaVersion = 26;
// Minor schema version. Should almost always be 0 (maybe bump on release
// branches if we have to).
const uint32_t kMinorSchemaVersion = 0;
// The schema version we store in the SQLite database is a (signed) 32-bit
// integer. The major version is left-shifted 4 bits so the max value is
// 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
static_assert(kMajorSchemaVersion <= 0xFFFFFFF,
"Major version needs to fit in 28 bits.");
static_assert(kMinorSchemaVersion <= 0xF,
"Minor version needs to fit in 4 bits.");
const int32_t kSQLiteSchemaVersion =
int32_t((kMajorSchemaVersion << 4) + kMinorSchemaVersion);
const int32_t kStorageProgressGranularity = 1000;
// Changing the value here will override the page size of new databases only.
// A journal mode change and VACUUM are needed to change existing databases, so
// the best way to do that is to use the schema version upgrade mechanism.
const uint32_t kSQLitePageSizeOverride =
#ifdef IDB_MOBILE
2048;
#else
4096;
#endif
static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 ||
(kSQLitePageSizeOverride % 2 == 0 &&
kSQLitePageSizeOverride >= 512 &&
kSQLitePageSizeOverride <= 65536),
"Must be 0 (disabled) or a power of 2 between 512 and 65536!");
// Set to -1 to use SQLite's default, 0 to disable, or some positive number to
// enforce a custom limit.
const int32_t kMaxWALPages = 5000; // 20MB on desktop, 10MB on mobile.
// Set to some multiple of the page size to grow the database in larger chunks.
const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2;
static_assert(kSQLiteGrowthIncrement >= 0 &&
kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
"Must be 0 (disabled) or a positive multiple of the page size!");
// The maximum number of threads that can be used for database activity at a
// single time.
const uint32_t kMaxConnectionThreadCount = 20;
static_assert(kMaxConnectionThreadCount, "Must have at least one thread!");
// The maximum number of threads to keep when idle. Threads that become idle in
// excess of this number will be shut down immediately.
const uint32_t kMaxIdleConnectionThreadCount = 2;
static_assert(kMaxConnectionThreadCount >= kMaxIdleConnectionThreadCount,
"Idle thread limit must be less than total thread limit!");
// The length of time that database connections will be held open after all
// transactions have completed before doing idle maintenance.
const uint32_t kConnectionIdleMaintenanceMS = 2 * 1000; // 2 seconds
// The length of time that database connections will be held open after all
// transactions and maintenance have completed.
const uint32_t kConnectionIdleCloseMS = 10 * 1000; // 10 seconds
// The length of time that idle threads will stay alive before being shut down.
const uint32_t kConnectionThreadIdleMS = 30 * 1000; // 30 seconds
#define SAVEPOINT_CLAUSE "SAVEPOINT sp;"
const uint32_t kFileCopyBufferSize = 32768;
constexpr auto kJournalDirectoryName = u"journals"_ns;
constexpr auto kFileManagerDirectoryNameSuffix = u".files"_ns;
constexpr auto kSQLiteSuffix = u".sqlite"_ns;
constexpr auto kSQLiteJournalSuffix = u".sqlite-journal"_ns;
constexpr auto kSQLiteSHMSuffix = u".sqlite-shm"_ns;
constexpr auto kSQLiteWALSuffix = u".sqlite-wal"_ns;
const char kPrefFileHandleEnabled[] = "dom.fileHandle.enabled";
constexpr auto kPermissionStringBase = "indexedDB-chrome-"_ns;
constexpr auto kPermissionReadSuffix = "-read"_ns;
constexpr auto kPermissionWriteSuffix = "-write"_ns;
// The following constants define all names of binding parameters in statements,
// where they are bound by name. This should include all parameter names which
// are bound by name. Binding may be done by index when the statement definition
// and binding are done in the same local scope, and no other reasons prevent
// using the indexes (e.g. multiple statement variants with differing number or
// order of parameters). Neither the styles of specifying parameter names
// (literally vs. via these constants) nor the binding styles (by index vs. by
// name) should not be mixed for the same statement. The decision must be made
// for each statement based on the proximity of statement and binding calls.
constexpr auto kStmtParamNameCurrentKey = "current_key"_ns;
constexpr auto kStmtParamNameRangeBound = "range_bound"_ns;
constexpr auto kStmtParamNameObjectStorePosition = "object_store_position"_ns;
constexpr auto kStmtParamNameLowerKey = "lower_key"_ns;
constexpr auto kStmtParamNameUpperKey = "upper_key"_ns;
constexpr auto kStmtParamNameKey = "key"_ns;
constexpr auto kStmtParamNameObjectStoreId = "object_store_id"_ns;
constexpr auto kStmtParamNameIndexId = "index_id"_ns;
// TODO: Maybe the uses of kStmtParamNameId should be replaced by more
// specific constants such as kStmtParamNameObjectStoreId.
constexpr auto kStmtParamNameId = "id"_ns;
constexpr auto kStmtParamNameValue = "value"_ns;
constexpr auto kStmtParamNameObjectDataKey = "object_data_key"_ns;
constexpr auto kStmtParamNameIndexDataValues = "index_data_values"_ns;
constexpr auto kStmtParamNameData = "data"_ns;
constexpr auto kStmtParamNameFileIds = "file_ids"_ns;
constexpr auto kStmtParamNameValueLocale = "value_locale"_ns;
constexpr auto kStmtParamNameLimit = "limit"_ns;
// The following constants define some names of columns in tables, which are
// referred to in remote locations, e.g. in calls to
// GetBindingClauseForKeyRange.
constexpr auto kColumnNameKey = "key"_ns;
constexpr auto kColumnNameValue = "value"_ns;
constexpr auto kColumnNameAliasSortKey = "sort_column"_ns;
// SQL fragments used at multiple locations.
constexpr auto kOpenLimit = " LIMIT "_ns;
// The deletion marker file is created before RemoveDatabaseFilesAndDirectory
// begins deleting a database. It is removed as the last step of deletion. If a
// deletion marker file is found when initializing the origin, the deletion
// routine is run again to ensure that the database and all of its related files
// are removed. The primary goal of this mechanism is to avoid situations where
// a database has been partially deleted, leading to inconsistent state for the
// origin.
constexpr auto kIdbDeletionMarkerFilePrefix = u"idb-deleting-"_ns;
const uint32_t kDeleteTimeoutMs = 1000;
/**
* Automatically crash the browser if IndexedDB shutdown takes this long. We've
* chosen a value that is longer than the value for QuotaManager shutdown timer
* which is currently set to 30 seconds. We've also chosen a value that is long
* long enough that it is unlikely for the problem to be falsely triggered by
* slow system I/O. We've also chosen a value long enough so that automated
* tests should time out and fail if IndexedDB shutdown hangs. Also, this value
* is long enough so that testers can notice the IndexedDB shutdown hang; we
* want to know about the hangs, not hide them. On the other hand this value is
* less than 60 seconds which is used by nsTerminator to crash a hung main
* process.
*/
#define SHUTDOWN_TIMEOUT_MS 50000
#ifdef DEBUG
const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
const uint32_t kDEBUGThreadSleepMS = 0;
const int32_t kDEBUGTransactionThreadPriority =
nsISupportsPriority::PRIORITY_NORMAL;
const uint32_t kDEBUGTransactionThreadSleepMS = 0;
#endif
/*******************************************************************************
* Metadata classes
******************************************************************************/
// Can be instantiated either on the QuotaManager IO thread or on a
// versionchange transaction thread. These threads can never race so this is
// totally safe.
struct FullIndexMetadata {
IndexMetadata mCommonMetadata = {0, nsString(), KeyPath(0), nsCString(),
false, false, false};
FlippedOnce<false> mDeleted;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullIndexMetadata)
private:
~FullIndexMetadata() = default;
};
typedef nsRefPtrHashtable<nsUint64HashKey, FullIndexMetadata> IndexTable;
// Can be instantiated either on the QuotaManager IO thread or on a
// versionchange transaction thread. These threads can never race so this is
// totally safe.
struct FullObjectStoreMetadata {
ObjectStoreMetadata mCommonMetadata = {0, nsString(), KeyPath(0), false};
IndexTable mIndexes;
// These two members are only ever touched on a transaction thread!
int64_t mNextAutoIncrementId = 0;
int64_t mCommittedAutoIncrementId = 0;
FlippedOnce<false> mDeleted;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);
bool HasLiveIndexes() const;
private:
~FullObjectStoreMetadata() = default;
};
typedef nsRefPtrHashtable<nsUint64HashKey, FullObjectStoreMetadata>
ObjectStoreTable;
using IndexOrObjectStoreId = int64_t;
static_assert(
std::is_same_v<
IndexOrObjectStoreId,
std::remove_cv_t<std::remove_reference_t<decltype(
std::declval<const ObjectStoreGetParams&>().objectStoreId())>>>);
static_assert(std::is_same_v<
IndexOrObjectStoreId,
std::remove_cv_t<std::remove_reference_t<decltype(
std::declval<const IndexGetParams&>().objectStoreId())>>>);
struct FullDatabaseMetadata final : AtomicSafeRefCounted<FullDatabaseMetadata> {
DatabaseMetadata mCommonMetadata;
nsCString mDatabaseId;
nsString mFilePath;
ObjectStoreTable mObjectStores;
IndexOrObjectStoreId mNextObjectStoreId = 0;
IndexOrObjectStoreId mNextIndexId = 0;
public:
explicit FullDatabaseMetadata(const DatabaseMetadata& aCommonMetadata)
: mCommonMetadata(aCommonMetadata) {
AssertIsOnBackgroundThread();
}
[[nodiscard]] SafeRefPtr<FullDatabaseMetadata> Duplicate() const;
MOZ_DECLARE_REFCOUNTED_TYPENAME(FullDatabaseMetadata)
};
template <class Enumerable>
auto MatchMetadataNameOrId(const Enumerable& aEnumerable,
IndexOrObjectStoreId aId,
Maybe<const nsAString&> aName = Nothing()) {
AssertIsOnBackgroundThread();
MOZ_ASSERT(aId);
const auto it = std::find_if(
aEnumerable.cbegin(), aEnumerable.cend(),
[aId, aName](const auto& entry) {
MOZ_ASSERT(entry.GetKey() != 0);
const auto& value = entry.GetData();
MOZ_ASSERT(value);
return !value->mDeleted &&
(aId == value->mCommonMetadata.id() ||
(aName && *aName == value->mCommonMetadata.name()));
});
return it != aEnumerable.cend() ? SomeRef(*it->GetData()) : Nothing();
}
struct IndexDataValue final {
IndexOrObjectStoreId mIndexId;
Key mPosition;
Key mLocaleAwarePosition;
bool mUnique;
IndexDataValue() : mIndexId(0), mUnique(false) {
MOZ_COUNT_CTOR(IndexDataValue);
}
IndexDataValue(IndexDataValue&& aOther)
: mIndexId(aOther.mIndexId),
mPosition(std::move(aOther.mPosition)),
mLocaleAwarePosition(std::move(aOther.mLocaleAwarePosition)),
mUnique(aOther.mUnique) {
MOZ_ASSERT(!aOther.mPosition.IsUnset());
MOZ_COUNT_CTOR(IndexDataValue);
}
IndexDataValue(IndexOrObjectStoreId aIndexId, bool aUnique,
const Key& aPosition)
: mIndexId(aIndexId), mPosition(aPosition), mUnique(aUnique) {
MOZ_ASSERT(!aPosition.IsUnset());
MOZ_COUNT_CTOR(IndexDataValue);
}
IndexDataValue(IndexOrObjectStoreId aIndexId, bool aUnique,
const Key& aPosition, const Key& aLocaleAwarePosition)
: mIndexId(aIndexId),
mPosition(aPosition),
mLocaleAwarePosition(aLocaleAwarePosition),
mUnique(aUnique) {
MOZ_ASSERT(!aPosition.IsUnset());
MOZ_COUNT_CTOR(IndexDataValue);
}
MOZ_COUNTED_DTOR(IndexDataValue)
bool operator==(const IndexDataValue& aOther) const {
if (mIndexId != aOther.mIndexId) {
return false;
}
if (mLocaleAwarePosition.IsUnset()) {
return mPosition == aOther.mPosition;
}
return mLocaleAwarePosition == aOther.mLocaleAwarePosition;
}
bool operator<(const IndexDataValue& aOther) const {
if (mIndexId == aOther.mIndexId) {
if (mLocaleAwarePosition.IsUnset()) {
return mPosition < aOther.mPosition;
}
return mLocaleAwarePosition < aOther.mLocaleAwarePosition;
}
return mIndexId < aOther.mIndexId;
}
};
/*******************************************************************************
* SQLite functions
******************************************************************************/
constexpr int32_t MakeSchemaVersion(uint32_t aMajorSchemaVersion,
uint32_t aMinorSchemaVersion) {
return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion);
}
// WARNING: the hash function used for the database name must not change.
// That's why this function exists separately from mozilla::HashString(), even
// though it is (at the time of writing) equivalent. See bug 780408 and bug
// 940315 for details.
uint32_t HashName(const nsAString& aName) {
struct Helper {
static uint32_t RotateBitsLeft32(uint32_t aValue, uint8_t aBits) {
MOZ_ASSERT(aBits < 32);
return (aValue << aBits) | (aValue >> (32 - aBits));
}
};
static const uint32_t kGoldenRatioU32 = 0x9e3779b9u;
return std::accumulate(aName.BeginReading(), aName.EndReading(), uint32_t(0),
[](uint32_t hash, char16_t ch) {
return kGoldenRatioU32 *
(Helper::RotateBitsLeft32(hash, 5) ^ ch);
});
}
nsresult ClampResultCode(nsresult aResultCode) {
if (NS_SUCCEEDED(aResultCode) ||
NS_ERROR_GET_MODULE(aResultCode) == NS_ERROR_MODULE_DOM_INDEXEDDB) {
return aResultCode;
}
switch (aResultCode) {
case NS_ERROR_FILE_NO_DEVICE_SPACE:
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
case NS_ERROR_STORAGE_CONSTRAINT:
return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
default:
#ifdef DEBUG
nsPrintfCString message("Converting non-IndexedDB error code (0x%" PRIX32
") to "
"NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR",
static_cast<uint32_t>(aResultCode));
NS_WARNING(message.get());
#else
;
#endif
}
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
void GetDatabaseFilenameBase(const nsAString& aDatabaseName,
nsAutoString& aDatabaseFilenameBase) {
MOZ_ASSERT(aDatabaseFilenameBase.IsEmpty());
// WARNING: do not change this hash function. See the comment in HashName()
// for details.
aDatabaseFilenameBase.AppendInt(HashName(aDatabaseName));
nsCString escapedName;
if (!NS_Escape(NS_ConvertUTF16toUTF8(aDatabaseName), escapedName,
url_XPAlphas)) {
MOZ_CRASH("Can't escape database name!");
}
const char* forwardIter = escapedName.BeginReading();
const char* backwardIter = escapedName.EndReading() - 1;
nsAutoCString substring;
while (forwardIter <= backwardIter && substring.Length() < 21) {
if (substring.Length() % 2) {
substring.Append(*backwardIter--);
} else {
substring.Append(*forwardIter++);
}
}
aDatabaseFilenameBase.AppendASCII(substring.get(), substring.Length());
}
uint32_t CompressedByteCountForNumber(uint64_t aNumber) {
// All bytes have 7 bits available.
uint32_t count = 1;
while ((aNumber >>= 7)) {
count++;
}
return count;
}
uint32_t CompressedByteCountForIndexId(IndexOrObjectStoreId aIndexId) {
MOZ_ASSERT(aIndexId);
MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
"Overflow!");
return CompressedByteCountForNumber(uint64_t(aIndexId * 2));
}
void WriteCompressedNumber(uint64_t aNumber, uint8_t** aIterator) {
MOZ_ASSERT(aIterator);
MOZ_ASSERT(*aIterator);
uint8_t*& buffer = *aIterator;
#ifdef DEBUG
const uint8_t* const bufferStart = buffer;
const uint64_t originalNumber = aNumber;
#endif
while (true) {
uint64_t shiftedNumber = aNumber >> 7;
if (shiftedNumber) {
*buffer++ = uint8_t(0x80 | (aNumber & 0x7f));
aNumber = shiftedNumber;
} else {
*buffer++ = uint8_t(aNumber);
break;
}
}
MOZ_ASSERT(buffer > bufferStart);
MOZ_ASSERT(uint32_t(buffer - bufferStart) ==
CompressedByteCountForNumber(originalNumber));
}
std::pair<uint64_t, mozilla::Span<const uint8_t>> ReadCompressedNumber(
const Span<const uint8_t> aSpan) {
uint8_t shiftCounter = 0;
uint64_t result = 0;
const auto end = aSpan.cend();
const auto newPos =
std::find_if(aSpan.cbegin(), end, [&result, &shiftCounter](uint8_t byte) {
MOZ_ASSERT(shiftCounter <= 56, "Shifted too many bits!");
result += (uint64_t(byte & 0x7f) << shiftCounter);
shiftCounter += 7;
return !(byte & 0x80);
});
if (NS_WARN_IF(newPos == end)) {
MOZ_ASSERT(false);
// XXX Shouldn't we return an error in this case?
}
return {result, Span{newPos + 1, end}};
}
void WriteCompressedIndexId(IndexOrObjectStoreId aIndexId, bool aUnique,
uint8_t** aIterator) {
MOZ_ASSERT(aIndexId);
MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
"Overflow!");
MOZ_ASSERT(aIterator);
MOZ_ASSERT(*aIterator);
const uint64_t indexId = (uint64_t(aIndexId * 2) | (aUnique ? 1 : 0));
WriteCompressedNumber(indexId, aIterator);
}
auto ReadCompressedIndexId(const Span<const uint8_t> aData) {
const auto [indexId, remainder] = ReadCompressedNumber(aData);
MOZ_ASSERT(UINT64_MAX / 2 >= uint64_t(indexId), "Bad index id!");
return std::tuple{IndexOrObjectStoreId(indexId >> 1), indexId % 2 == 1,
remainder};
}
// static
nsresult MakeCompressedIndexDataValues(
const nsTArray<IndexDataValue>& aIndexValues,
UniqueFreePtr<uint8_t>& aCompressedIndexDataValues,
uint32_t* aCompressedIndexDataValuesLength) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
MOZ_ASSERT(!aCompressedIndexDataValues);
MOZ_ASSERT(aCompressedIndexDataValuesLength);
AUTO_PROFILER_LABEL("MakeCompressedIndexDataValues", DOM);
const uint32_t arrayLength = aIndexValues.Length();
if (!arrayLength) {
*aCompressedIndexDataValuesLength = 0;
return NS_OK;
}
// First calculate the size of the final buffer.
uint32_t blobDataLength = 0;
for (const IndexDataValue& info : aIndexValues) {
const nsCString& keyBuffer = info.mPosition.GetBuffer();
const nsCString& sortKeyBuffer = info.mLocaleAwarePosition.GetBuffer();
const uint32_t keyBufferLength = keyBuffer.Length();
const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
MOZ_ASSERT(!keyBuffer.IsEmpty());
const CheckedUint32 infoLength =
CheckedUint32(CompressedByteCountForIndexId(info.mIndexId)) +
CompressedByteCountForNumber(keyBufferLength) +
CompressedByteCountForNumber(sortKeyBufferLength) + keyBufferLength +
sortKeyBufferLength;
// Don't let |infoLength| overflow.
if (NS_WARN_IF(!infoLength.isValid())) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
// Don't let |blobDataLength| overflow.
if (NS_WARN_IF(UINT32_MAX - infoLength.value() < blobDataLength)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
blobDataLength += infoLength.value();
}
UniqueFreePtr<uint8_t> blobData(
static_cast<uint8_t*>(malloc(blobDataLength)));
if (NS_WARN_IF(!blobData)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_OUT_OF_MEMORY;
}
uint8_t* blobDataIter = blobData.get();
for (const IndexDataValue& info : aIndexValues) {
const nsCString& keyBuffer = info.mPosition.GetBuffer();
const nsCString& sortKeyBuffer = info.mLocaleAwarePosition.GetBuffer();
const uint32_t keyBufferLength = keyBuffer.Length();
const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter);
WriteCompressedNumber(keyBufferLength, &blobDataIter);
memcpy(blobDataIter, keyBuffer.get(), keyBufferLength);
blobDataIter += keyBufferLength;
WriteCompressedNumber(sortKeyBufferLength, &blobDataIter);
memcpy(blobDataIter, sortKeyBuffer.get(), sortKeyBufferLength);
blobDataIter += sortKeyBufferLength;
}
MOZ_ASSERT(blobDataIter == blobData.get() + blobDataLength);
aCompressedIndexDataValues = std::move(blobData);
*aCompressedIndexDataValuesLength = uint32_t(blobDataLength);
return NS_OK;
}
// aOutIndexValues is an output parameter, since its storage is reused.
nsresult ReadCompressedIndexDataValuesFromBlob(
const Span<const uint8_t> aBlobData,
nsTArray<IndexDataValue>& aOutIndexValues) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
MOZ_ASSERT(!aBlobData.IsEmpty());
MOZ_ASSERT(aOutIndexValues.IsEmpty());
AUTO_PROFILER_LABEL("ReadCompressedIndexDataValuesFromBlob", DOM);
// XXX Is this check still necessary with a Span? Or should it rather be moved
// to the caller?
if (uintptr_t(aBlobData.Elements()) > UINTPTR_MAX - aBlobData.LengthBytes()) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_FILE_CORRUPTED;
}
for (auto remainder = aBlobData; !remainder.IsEmpty();) {
const auto [indexId, unique, remainderAfterIndexId] =
ReadCompressedIndexId(remainder);
if (NS_WARN_IF(remainderAfterIndexId.IsEmpty())) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_FILE_CORRUPTED;
}
// Read key buffer length.
const auto [keyBufferLength, remainderAfterKeyBufferLength] =
ReadCompressedNumber(remainderAfterIndexId);
if (NS_WARN_IF(remainderAfterKeyBufferLength.IsEmpty()) ||
NS_WARN_IF(keyBufferLength > uint64_t(UINT32_MAX)) ||
NS_WARN_IF(keyBufferLength > remainderAfterKeyBufferLength.Length())
// XXX Does this sub-condition make any sense?
// || NS_WARN_IF(keyBufferLength > uintptr_t(blobDataEnd))
) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_FILE_CORRUPTED;
}
const auto [keyBuffer, remainderAfterKeyBuffer] =
remainderAfterKeyBufferLength.SplitAt(keyBufferLength);
auto idv =
IndexDataValue{indexId, unique, Key{nsCString{AsChars(keyBuffer)}}};
// Read sort key buffer length.
const auto [sortKeyBufferLength, remainderAfterSortKeyBufferLength] =
ReadCompressedNumber(remainderAfterKeyBuffer);
remainder = remainderAfterSortKeyBufferLength;
if (sortKeyBufferLength > 0) {
if (NS_WARN_IF(remainder.IsEmpty()) ||
NS_WARN_IF(sortKeyBufferLength > uint64_t(UINT32_MAX)) ||
NS_WARN_IF(sortKeyBufferLength > remainder.Length())
// XXX Does this sub-condition make any sense?
// || NS_WARN_IF(sortKeyBufferLength > uintptr_t(blobDataEnd))
) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_FILE_CORRUPTED;
}
const auto [sortKeyBuffer, remainderAfterSortKeyBuffer] =
remainder.SplitAt(sortKeyBufferLength);
idv.mLocaleAwarePosition = Key{nsCString{AsChars(sortKeyBuffer)}};
remainder = remainderAfterSortKeyBuffer;
}
if (NS_WARN_IF(!aOutIndexValues.AppendElement(std::move(idv), fallible))) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_OUT_OF_MEMORY;
}
}
aOutIndexValues.Sort();
return NS_OK;
}
// aOutIndexValues is an output parameter, since its storage is reused.
template <typename T>
nsresult ReadCompressedIndexDataValuesFromSource(
T& aSource, uint32_t aColumnIndex,
nsTArray<IndexDataValue>& aOutIndexValues) {
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
MOZ_ASSERT(aOutIndexValues.IsEmpty());
int32_t columnType;
nsresult rv = aSource.GetTypeOfIndex(aColumnIndex, &columnType);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) {
return NS_OK;
}
MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_BLOB);
const uint8_t* blobData;
uint32_t blobDataLength;
rv = aSource.GetSharedBlob(aColumnIndex, &blobDataLength, &blobData);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (NS_WARN_IF(!blobDataLength)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_FILE_CORRUPTED;
}
rv = ReadCompressedIndexDataValuesFromBlob(MakeSpan(blobData, blobDataLength),
aOutIndexValues);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
// aOutIndexValues is an output parameter, since its storage is reused.
nsresult ReadCompressedIndexDataValues(
mozIStorageStatement& aStatement, uint32_t aColumnIndex,
nsTArray<IndexDataValue>& aOutIndexValues) {
return ReadCompressedIndexDataValuesFromSource(aStatement, aColumnIndex,
aOutIndexValues);
}
using IndexDataValuesAutoArray = AutoTArray<IndexDataValue, 32>;
Result<IndexDataValuesAutoArray, nsresult> ReadCompressedIndexDataValues(
mozIStorageValueArray& aValues, uint32_t aColumnIndex) {
IndexDataValuesAutoArray result;
const nsresult rv =
ReadCompressedIndexDataValuesFromSource(aValues, aColumnIndex, result);
if (NS_WARN_IF(NS_FAILED(rv))) {
return Err(rv);
}
return result;
}
nsresult CreateFileTables(mozIStorageConnection& aConnection) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("CreateFileTables", DOM);
// Table `file`
nsresult rv =
aConnection.ExecuteSimpleSQL(nsLiteralCString("CREATE TABLE file ("
"id INTEGER PRIMARY KEY, "
"refcount INTEGER NOT NULL"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("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;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(nsLiteralCString(
"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;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("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;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("CREATE TRIGGER file_update_trigger "
"AFTER UPDATE ON file "
"FOR EACH ROW WHEN NEW.refcount = 0 "
"BEGIN "
"DELETE FROM file WHERE id = OLD.id; "
"END;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult CreateTables(mozIStorageConnection& aConnection) {
AssertIsOnIOThread();
AUTO_PROFILER_LABEL("CreateTables", DOM);
// Table `database`
// There are two reasons for having the origin column.
// First, we can ensure that we don't have collisions in the origin hash we
// use for the path because when we open the db we can make sure that the
// origins exactly match. Second, chrome code crawling through the idb
// directory can figure out the origin of every db without having to
// reverse-engineer our hash scheme.
nsresult rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("CREATE TABLE database"
"( 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;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Table `object_store`
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("CREATE TABLE object_store"
"( id INTEGER PRIMARY KEY"
", auto_increment INTEGER NOT NULL DEFAULT 0"
", name TEXT NOT NULL"
", key_path TEXT"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Table `object_store_index`
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("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"
", locale TEXT"
", is_auto_locale BOOLEAN NOT NULL"
", FOREIGN KEY (object_store_id) "
"REFERENCES object_store(id) "
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Table `object_data`
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("CREATE TABLE object_data"
"( 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;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Table `index_data`
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("CREATE TABLE index_data"
"( index_id INTEGER NOT NULL"
", value BLOB NOT NULL"
", object_data_key BLOB NOT NULL"
", object_store_id INTEGER NOT NULL"
", value_locale BLOB"
", 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;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(nsLiteralCString(
"CREATE INDEX index_data_value_locale_index "
"ON index_data (index_id, value_locale, object_data_key, value) "
"WHERE value_locale IS NOT NULL;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Table `unique_index_data`
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("CREATE TABLE unique_index_data"
"( index_id INTEGER NOT NULL"
", value BLOB NOT NULL"
", object_store_id INTEGER NOT NULL"
", object_data_key BLOB NOT NULL"
", value_locale BLOB"
", 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;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(nsLiteralCString(
"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;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = CreateFileTables(aConnection);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.SetSchemaVersion(kSQLiteSchemaVersion);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
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(
nsLiteralCString("SELECT name, version, dataVersion "
"FROM database"),
getter_AddRefs(stmt));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsString name;
int32_t intVersion;
int64_t dataVersion;
{
mozStorageStatementScoper scoper(stmt);
bool hasResults;
rv = stmt->ExecuteStep(&hasResults);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
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(
nsLiteralCString("CREATE TABLE database ("
"name TEXT NOT NULL, "
"version INTEGER NOT NULL DEFAULT 0, "
"dataVersion INTEGER NOT NULL"
");"));
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(
nsLiteralCString("INSERT INTO database (name, version, dataVersion) "
"VALUES (:name, :version, :dataVersion)"),
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(
nsLiteralCString("CREATE TEMPORARY TABLE temp_upgrade ("
"id INTEGER PRIMARY KEY, "
"object_store_id, "
"key_value, "
"data "
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO temp_upgrade "
"SELECT id, object_store_id, key_value, data "
"FROM object_data;"));
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(nsLiteralCString(
"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"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO object_data "
"SELECT id, object_store_id, key_value, data "
"FROM temp_upgrade;"));
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(
nsLiteralCString("CREATE TEMPORARY TABLE temp_upgrade ("
"id INTEGER PRIMARY KEY, "
"object_store_id, "
"data "
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO temp_upgrade "
"SELECT id, object_store_id, data "
"FROM ai_object_data;"));
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(nsLiteralCString(
"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"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO ai_object_data "
"SELECT id, object_store_id, data "
"FROM temp_upgrade;"));
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(
nsLiteralCString("CREATE TEMPORARY TABLE temp_upgrade ("
"index_id, "
"value, "
"object_data_key, "
"object_data_id "
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(nsLiteralCString(
"INSERT INTO temp_upgrade "
"SELECT index_id, value, object_data_key, object_data_id "
"FROM index_data;"));
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(nsLiteralCString(
"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"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(nsLiteralCString(
"INSERT OR IGNORE INTO index_data "
"SELECT index_id, value, object_data_key, object_data_id "
"FROM temp_upgrade;"));
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(
nsLiteralCString("CREATE INDEX index_data_object_data_id_index "
"ON index_data (object_data_id);"));
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(
nsLiteralCString("CREATE TEMPORARY TABLE temp_upgrade ("
"index_id, "
"value, "
"object_data_key, "
"object_data_id "
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(nsLiteralCString(
"INSERT INTO temp_upgrade "
"SELECT index_id, value, object_data_key, object_data_id "
"FROM unique_index_data;"));
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(nsLiteralCString(
"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"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(nsLiteralCString(
"INSERT INTO unique_index_data "
"SELECT index_id, value, object_data_key, object_data_id "
"FROM temp_upgrade;"));
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(
nsLiteralCString("CREATE INDEX unique_index_data_object_data_id_index "
"ON unique_index_data (object_data_id);"));
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(
nsLiteralCString("CREATE TEMPORARY TABLE temp_upgrade ("
"index_id, "
"value, "
"ai_object_data_id "
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO temp_upgrade "
"SELECT index_id, value, ai_object_data_id "
"FROM ai_index_data;"));
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(nsLiteralCString(
"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"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT OR IGNORE INTO ai_index_data "
"SELECT index_id, value, ai_object_data_id "
"FROM temp_upgrade;"));
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(
nsLiteralCString("CREATE INDEX ai_index_data_ai_object_data_id_index "
"ON ai_index_data (ai_object_data_id);"));
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(
nsLiteralCString("CREATE TEMPORARY TABLE temp_upgrade ("
"index_id, "
"value, "
"ai_object_data_id "
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO temp_upgrade "
"SELECT index_id, value, ai_object_data_id "
"FROM ai_unique_index_data;"));
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(nsLiteralCString(
"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"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO ai_unique_index_data "
"SELECT index_id, value, ai_object_data_id "
"FROM temp_upgrade;"));
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(nsLiteralCString(
"CREATE INDEX ai_unique_index_data_ai_object_data_id_index "
"ON ai_unique_index_data (ai_object_data_id);"));
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(
nsLiteralCString("CREATE TEMPORARY TABLE temp_upgrade ("
"id, "
"name, "
"key_path, "
"auto_increment"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO temp_upgrade "
"SELECT id, name, key_path, auto_increment "
"FROM object_store;"));
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(
nsLiteralCString("CREATE TABLE object_store ("
"id INTEGER PRIMARY KEY, "
"auto_increment INTEGER NOT NULL DEFAULT 0, "
"name TEXT NOT NULL, "
"key_path TEXT, "
"UNIQUE (name)"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO object_store "
"SELECT id, auto_increment, name, nullif(key_path, '') "
"FROM temp_upgrade;"));
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(
nsLiteralCString("CREATE TEMPORARY TABLE temp_upgrade ("
"id, "
"object_store_id, "
"name, "
"key_path, "
"unique_index, "
"object_store_autoincrement"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO temp_upgrade "
"SELECT id, object_store_id, name, key_path, "
"unique_index, object_store_autoincrement "
"FROM object_store_index;"));
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(nsLiteralCString(
"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"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO object_store_index "
"SELECT id, object_store_id, name, key_path, "
"unique_index, 0, object_store_autoincrement "
"FROM temp_upgrade;"));
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(
nsLiteralCString("CREATE TEMPORARY TABLE temp_upgrade ("
"id, "
"object_store_id, "
"name, "
"key_path, "
"unique_index, "
"multientry"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO temp_upgrade "
"SELECT id, object_store_id, name, key_path, "
"unique_index, multientry "
"FROM object_store_index;"));
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(nsLiteralCString(
"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"
");"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("INSERT INTO object_store_index "
"SELECT id, object_store_id, name, key_path, "
"unique_index, multientry "
"FROM temp_upgrade;"));
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(nsLiteralCString(
"INSERT INTO object_data (object_store_id, key_value, data, file_ids) "
"SELECT object_store_id, id, data, file_ids "
"FROM ai_object_data;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(
nsLiteralCString("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;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(nsLiteralCString(
"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;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(nsLiteralCString(
"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;"));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection.ExecuteSimpleSQL(nsLiteralCString(
"UPDATE object_store "
"SET auto_increment = (SELECT max(id) FROM ai_object_data) + 1 "
"WHERE auto_increment;"));
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);