Mirror of roytam1's UXP fork just in case Moonchild and Tobin decide to go after him
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

29646 lines
782 KiB

<
/* -*- Mode: C++; tab-width: 2; 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 <stdint.h> // UINTPTR_MAX, uintptr_t
#include "FileInfo.h"
#include "FileManager.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/Attributes.h"
#include "mozilla/AppProcessChecker.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/Casting.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/EndianUtils.h"
#include "mozilla/ErrorNames.h"
#include "mozilla/LazyIdleThread.h"
#include "mozilla/Maybe.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/SnappyCompressOutputStream.h"
#include "mozilla/SnappyUncompressInputStream.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/storage.h"
#include "mozilla/Unused.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/StructuredCloneTags.h"
#include "mozilla/dom/TabParent.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/indexedDB/PIndexedDBPermissionRequestParent.h"
#include "mozilla/dom/ipc/BlobParent.h"
#include "mozilla/dom/quota/Client.h"
#include "mozilla/dom/quota/FileStreams.h"
#include "mozilla/dom/quota/OriginScope.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/Scoped.h"
#include "mozilla/storage/Variant.h"
#include "nsAutoPtr.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsClassHashtable.h"
#include "nsCOMPtr.h"
#include "nsDataHashtable.h"
#include "nsEscape.h"
#include "nsHashKeys.h"
#include "nsNetUtil.h"
#include "nsISimpleEnumerator.h"
#include "nsIAppsService.h"
#include "nsIEventTarget.h"
#include "nsIFile.h"
#include "nsIFileURL.h"
#include "nsIFileProtocolHandler.h"
#include "nsIInputStream.h"
#include "nsIInterfaceRequestor.h"
#include "nsInterfaceHashtable.h"
#include "nsIOutputStream.h"
#include "nsIPipe.h"
#include "nsIPrincipal.h"
#include "nsIScriptSecurityManager.h"
#include "nsISupports.h"
#include "nsISupportsImpl.h"
#include "nsISupportsPriority.h"
#include "nsIThread.h"
#include "nsITimer.h"
#include "nsIURI.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 "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
#define BLOB_IMPL_STORED_FILE_IID \
{0x6b505c84, 0x2c60, 0x4ffb, {0x8b, 0x91, 0xfe, 0x22, 0xb1, 0xec, 0x75, 0xe2}}
namespace mozilla {
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc,
PRFileDesc,
PR_Close);
namespace dom {
namespace indexedDB {
using namespace mozilla::dom::quota;
using namespace mozilla::ipc;
namespace {
class ConnectionPool;
class Cursor;
class Database;
struct DatabaseActorInfo;
class DatabaseFile;
class DatabaseLoggingInfo;
class DatabaseMaintenance;
class Factory;
class Maintenance;
class MutableFile;
class OpenDatabaseOp;
class TransactionBase;
class TransactionDatabaseOperationBase;
class VersionChangeTransaction;
/*******************************************************************************
* 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 = 25;
// 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;
#define JOURNAL_DIRECTORY_NAME "journals"
const char kFileManagerDirectoryNameSuffix[] = ".files";
const char kSQLiteJournalSuffix[] = ".sqlite-journal";
const char kSQLiteSHMSuffix[] = ".sqlite-shm";
const char kSQLiteWALSuffix[] = ".sqlite-wal";
const char kPrefFileHandleEnabled[] = "dom.fileHandle.enabled";
#define IDB_PREFIX "indexedDB"
#define PERMISSION_STRING_CHROME_BASE IDB_PREFIX "-chrome-"
#define PERMISSION_STRING_CHROME_READ_SUFFIX "-read"
#define PERMISSION_STRING_CHROME_WRITE_SUFFIX "-write"
#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
template <size_t N>
constexpr size_t
LiteralStringLength(const char (&aArr)[N])
{
static_assert(N, "Zero-length string literal?!");
// Don't include the null terminator.
return N - 1;
}
/*******************************************************************************
* Metadata classes
******************************************************************************/
struct FullIndexMetadata
{
IndexMetadata mCommonMetadata;
bool mDeleted;
public:
FullIndexMetadata()
: mCommonMetadata(0, nsString(), KeyPath(0), nsCString(), false, false, false)
, mDeleted(false)
{
// This can happen either on the QuotaManager IO thread or on a
// versionchange transaction thread. These threads can never race so this is
// totally safe.
}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullIndexMetadata)
private:
~FullIndexMetadata()
{ }
};
typedef nsRefPtrHashtable<nsUint64HashKey, FullIndexMetadata> IndexTable;
struct FullObjectStoreMetadata
{
ObjectStoreMetadata mCommonMetadata;
IndexTable mIndexes;
// These two members are only ever touched on a transaction thread!
int64_t mNextAutoIncrementId;
int64_t mCommittedAutoIncrementId;
bool mDeleted;
public:
FullObjectStoreMetadata()
: mCommonMetadata(0, nsString(), KeyPath(0), false)
, mNextAutoIncrementId(0)
, mCommittedAutoIncrementId(0)
, mDeleted(false)
{
// This can happen either on the QuotaManager IO thread or on a
// versionchange transaction thread. These threads can never race so this is
// totally safe.
}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);
bool
HasLiveIndexes() const;
private:
~FullObjectStoreMetadata()
{ }
};
typedef nsRefPtrHashtable<nsUint64HashKey, FullObjectStoreMetadata>
ObjectStoreTable;
struct FullDatabaseMetadata
{
DatabaseMetadata mCommonMetadata;
nsCString mDatabaseId;
nsString mFilePath;
ObjectStoreTable mObjectStores;
int64_t mNextObjectStoreId;
int64_t mNextIndexId;
public:
explicit FullDatabaseMetadata(const DatabaseMetadata& aCommonMetadata)
: mCommonMetadata(aCommonMetadata)
, mNextObjectStoreId(0)
, mNextIndexId(0)
{
AssertIsOnBackgroundThread();
}
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullDatabaseMetadata)
already_AddRefed<FullDatabaseMetadata>
Duplicate() const;
private:
~FullDatabaseMetadata()
{ }
};
template <class MetadataType>
class MOZ_STACK_CLASS MetadataNameOrIdMatcher final
{
typedef MetadataNameOrIdMatcher<MetadataType> SelfType;
const int64_t mId;
const nsString mName;
RefPtr<MetadataType> mMetadata;
bool mCheckName;
public:
template <class Enumerable>
static MetadataType*
Match(const Enumerable& aEnumerable, uint64_t aId, const nsAString& aName)
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(aId);
SelfType closure(aId, aName);
MatchHelper(aEnumerable, &closure);
return closure.mMetadata;
}
template <class Enumerable>
static MetadataType*
Match(const Enumerable& aEnumerable, uint64_t aId)
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(aId);
SelfType closure(aId);
MatchHelper(aEnumerable, &closure);
return closure.mMetadata;
}
private:
MetadataNameOrIdMatcher(const int64_t& aId, const nsAString& aName)
: mId(aId)
, mName(PromiseFlatString(aName))
, mMetadata(nullptr)
, mCheckName(true)
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(aId);
}
explicit MetadataNameOrIdMatcher(const int64_t& aId)
: mId(aId)
, mMetadata(nullptr)
, mCheckName(false)
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(aId);
}
template <class Enumerable>
static void
MatchHelper(const Enumerable& aEnumerable, SelfType* aClosure)
{
AssertIsOnBackgroundThread();
MOZ_ASSERT(aClosure);
for (auto iter = aEnumerable.ConstIter(); !iter.Done(); iter.Next()) {
#ifdef DEBUG
const uint64_t key = iter.Key();
#endif
MetadataType* value = iter.UserData();
MOZ_ASSERT(key != 0);
MOZ_ASSERT(value);
if (!value->mDeleted &&
(aClosure->mId == value->mCommonMetadata.id() ||
(aClosure->mCheckName &&
aClosure->mName == value->mCommonMetadata.name()))) {
aClosure->mMetadata = value;
break;
}
}
}
};
struct IndexDataValue final
{
int64_t mIndexId;
Key mKey;
Key mSortKey;
bool mUnique;
IndexDataValue()
: mIndexId(0)
, mUnique(false)
{
MOZ_COUNT_CTOR(IndexDataValue);
}
explicit
IndexDataValue(const IndexDataValue& aOther)
: mIndexId(aOther.mIndexId)
, mKey(aOther.mKey)
, mSortKey(aOther.mSortKey)
, mUnique(aOther.mUnique)
{
MOZ_ASSERT(!aOther.mKey.IsUnset());
MOZ_COUNT_CTOR(IndexDataValue);
}
IndexDataValue(int64_t aIndexId, bool aUnique, const Key& aKey)
: mIndexId(aIndexId)
, mKey(aKey)
, mUnique(aUnique)
{
MOZ_ASSERT(!aKey.IsUnset());
MOZ_COUNT_CTOR(IndexDataValue);
}
IndexDataValue(int64_t aIndexId, bool aUnique, const Key& aKey,
const Key& aSortKey)
: mIndexId(aIndexId)
, mKey(aKey)
, mSortKey(aSortKey)
, mUnique(aUnique)
{
MOZ_ASSERT(!aKey.IsUnset());
MOZ_COUNT_CTOR(IndexDataValue);
}
~IndexDataValue()
{
MOZ_COUNT_DTOR(IndexDataValue);
}
bool
operator==(const IndexDataValue& aOther) const
{
if (mIndexId != aOther.mIndexId) {
return false;
}
if (mSortKey.IsUnset()) {
return mKey == aOther.mKey;
}
return mSortKey == aOther.mSortKey;
}
bool
operator<(const IndexDataValue& aOther) const
{
if (mIndexId == aOther.mIndexId) {
if (mSortKey.IsUnset()) {
return mKey < aOther.mKey;
}
return mSortKey < aOther.mSortKey;
}
return mIndexId < aOther.mIndexId;
}
};
/*******************************************************************************
* SQLite functions
******************************************************************************/
int32_t
MakeSchemaVersion(uint32_t aMajorSchemaVersion,
uint32_t aMinorSchemaVersion)
{
return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion);
}
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;
const char16_t* str = aName.BeginReading();
size_t length = aName.Length();
uint32_t hash = 0;
for (size_t i = 0; i < length; i++) {
hash = kGoldenRatioU32 * (Helper::RotateBitsLeft32(hash, 5) ^ str[i]);
}
return hash;
}
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%X) to "
"NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR",
aResultCode);
NS_WARNING(message.get());
#else
;
#endif
}
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
}
void
GetDatabaseFilename(const nsAString& aName,
nsAutoString& aDatabaseFilename)
{
MOZ_ASSERT(aDatabaseFilename.IsEmpty());
aDatabaseFilename.AppendInt(HashName(aName));
nsCString escapedName;
if (!NS_Escape(NS_ConvertUTF16toUTF8(aName), 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++);
}
}
aDatabaseFilename.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(int64_t 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* 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));
}
uint64_t
ReadCompressedNumber(const uint8_t** aIterator, const uint8_t* aEnd)
{
MOZ_ASSERT(aIterator);
MOZ_ASSERT(*aIterator);
MOZ_ASSERT(aEnd);
MOZ_ASSERT(*aIterator < aEnd);
const uint8_t*& buffer = *aIterator;
uint8_t shiftCounter = 0;
uint64_t result = 0;
while (true) {
MOZ_ASSERT(shiftCounter <= 56, "Shifted too many bits!");
result += (uint64_t(*buffer & 0x7f) << shiftCounter);
shiftCounter += 7;
if (!(*buffer++ & 0x80)) {
break;
}
if (NS_WARN_IF(buffer == aEnd)) {
MOZ_ASSERT(false);
break;
}
}
return result;
}
void
WriteCompressedIndexId(int64_t 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);
}
void
ReadCompressedIndexId(const uint8_t** aIterator,
const uint8_t* aEnd,
int64_t* aIndexId,
bool* aUnique)
{
MOZ_ASSERT(aIterator);
MOZ_ASSERT(*aIterator);
MOZ_ASSERT(aIndexId);
MOZ_ASSERT(aUnique);
uint64_t indexId = ReadCompressedNumber(aIterator, aEnd);
if (indexId % 2) {
*aUnique = true;
indexId--;
} else {
*aUnique = false;
}
MOZ_ASSERT(UINT64_MAX / 2 >= uint64_t(indexId), "Bad index id!");
*aIndexId = int64_t(indexId / 2);
}
// static
nsresult
MakeCompressedIndexDataValues(
const FallibleTArray<IndexDataValue>& aIndexValues,
UniqueFreePtr<uint8_t>& aCompressedIndexDataValues,
uint32_t* aCompressedIndexDataValuesLength)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
MOZ_ASSERT(!aCompressedIndexDataValues);
MOZ_ASSERT(aCompressedIndexDataValuesLength);
PROFILER_LABEL("IndexedDB",
"MakeCompressedIndexDataValues",
js::ProfileEntry::Category::STORAGE);
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 (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
const IndexDataValue& info = aIndexValues[arrayIndex];
const nsCString& keyBuffer = info.mKey.GetBuffer();
const nsCString& sortKeyBuffer = info.mSortKey.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 (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
const IndexDataValue& info = aIndexValues[arrayIndex];
const nsCString& keyBuffer = info.mKey.GetBuffer();
const nsCString& sortKeyBuffer = info.mSortKey.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.swap(blobData);
*aCompressedIndexDataValuesLength = uint32_t(blobDataLength);
return NS_OK;
}
nsresult
ReadCompressedIndexDataValuesFromBlob(const uint8_t* aBlobData,
uint32_t aBlobDataLength,
nsTArray<IndexDataValue>& aIndexValues)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
MOZ_ASSERT(aBlobData);
MOZ_ASSERT(aBlobDataLength);
MOZ_ASSERT(aIndexValues.IsEmpty());
PROFILER_LABEL("IndexedDB",
"ReadCompressedIndexDataValuesFromBlob",
js::ProfileEntry::Category::STORAGE);
if (uintptr_t(aBlobData) > UINTPTR_MAX - aBlobDataLength) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_FILE_CORRUPTED;
}
const uint8_t* blobDataIter = aBlobData;
const uint8_t* blobDataEnd = aBlobData + aBlobDataLength;
while (blobDataIter < blobDataEnd) {
int64_t indexId;
bool unique;
ReadCompressedIndexId(&blobDataIter, blobDataEnd, &indexId, &unique);
if (NS_WARN_IF(blobDataIter == blobDataEnd)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_FILE_CORRUPTED;
}
// Read key buffer length.
const uint64_t keyBufferLength =
ReadCompressedNumber(&blobDataIter, blobDataEnd);
if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
NS_WARN_IF(keyBufferLength > uint64_t(UINT32_MAX)) ||
NS_WARN_IF(keyBufferLength > uintptr_t(blobDataEnd)) ||
NS_WARN_IF(blobDataIter > blobDataEnd - keyBufferLength)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_FILE_CORRUPTED;
}
nsCString keyBuffer(reinterpret_cast<const char*>(blobDataIter),
uint32_t(keyBufferLength));
blobDataIter += keyBufferLength;
IndexDataValue idv(indexId, unique, Key(keyBuffer));
// Read sort key buffer length.
const uint64_t sortKeyBufferLength =
ReadCompressedNumber(&blobDataIter, blobDataEnd);
if (sortKeyBufferLength > 0) {
if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
NS_WARN_IF(sortKeyBufferLength > uint64_t(UINT32_MAX)) ||
NS_WARN_IF(sortKeyBufferLength > uintptr_t(blobDataEnd)) ||
NS_WARN_IF(blobDataIter > blobDataEnd - sortKeyBufferLength)) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_FILE_CORRUPTED;
}
nsCString sortKeyBuffer(reinterpret_cast<const char*>(blobDataIter),
uint32_t(sortKeyBufferLength));
blobDataIter += sortKeyBufferLength;
idv.mSortKey = Key(sortKeyBuffer);
}
if (NS_WARN_IF(!aIndexValues.InsertElementSorted(idv, fallible))) {
IDB_REPORT_INTERNAL_ERR();
return NS_ERROR_OUT_OF_MEMORY;
}
}
MOZ_ASSERT(blobDataIter == blobDataEnd);
return NS_OK;
}
// static
template <typename T>
nsresult
ReadCompressedIndexDataValuesFromSource(T* aSource,
uint32_t aColumnIndex,
nsTArray<IndexDataValue>& aIndexValues)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(!IsOnBackgroundThread());
MOZ_ASSERT(aSource);
MOZ_ASSERT(aIndexValues.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(blobData,
blobDataLength,
aIndexValues);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult
ReadCompressedIndexDataValues(mozIStorageStatement* aStatement,
uint32_t aColumnIndex,
nsTArray<IndexDataValue>& aIndexValues)
{
return ReadCompressedIndexDataValuesFromSource(aStatement,
aColumnIndex,
aIndexValues);
}
nsresult
ReadCompressedIndexDataValues(mozIStorageValueArray* aValues,
uint32_t aColumnIndex,
nsTArray<IndexDataValue>& aIndexValues)
{
return ReadCompressedIndexDataValuesFromSource(aValues,
aColumnIndex,
aIndexValues);
}
nsresult
CreateFileTables(mozIStorageConnection* aConnection)
{
AssertIsOnIOThread();
MOZ_ASSERT(aConnection);
PROFILER_LABEL("IndexedDB",
"CreateFileTables",
js::ProfileEntry::Category::STORAGE);
// Table `file`
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE file ("
"id INTEGER PRIMARY KEY, "
"refcount INTEGER NOT NULL"
");"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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();
MOZ_ASSERT(aConnection);
PROFILER_LABEL("IndexedDB",
"CreateTables",
js::ProfileEntry::Category::STORAGE);
// 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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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();
MOZ_ASSERT(aConnection);
PROFILER_LABEL("IndexedDB",
"UpgradeSchemaFrom4To5",
js::ProfileEntry::Category::STORAGE);
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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE database"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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;
}
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
"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->BindStringParameter(0, name);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = stmt->BindInt32Parameter(1, intVersion);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = stmt->BindInt64Parameter(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();
MOZ_ASSERT(aConnection);
PROFILER_LABEL("IndexedDB",
"UpgradeSchemaFrom5To6",
js::ProfileEntry::Category::STORAGE);
// First, drop all the indexes we're no longer going to use.
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX key_index;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX ai_key_index;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX value_index;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX ai_value_index;"
));
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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE object_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE temp_upgrade;"
));
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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE ai_object_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE temp_upgrade;"
));
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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE index_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE temp_upgrade;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE unique_index_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE temp_upgrade;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"CREATE TEMPORARY TABLE temp_upgrade ("
"index_id, "
"value, "
"ai_object_data_id "
");"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE ai_index_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE temp_upgrade;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"CREATE TEMPORARY TABLE temp_upgrade ("
"index_id, "
"value, "
"ai_object_data_id "
");"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE ai_unique_index_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE temp_upgrade;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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();
MOZ_ASSERT(aConnection);
PROFILER_LABEL("IndexedDB",
"UpgradeSchemaFrom6To7",
js::ProfileEntry::Category::STORAGE);
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TEMPORARY TABLE temp_upgrade ("
"id, "
"name, "
"key_path, "
"auto_increment"
");"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE object_store;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE temp_upgrade;"
));
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();
MOZ_ASSERT(aConnection);
PROFILER_LABEL("IndexedDB",
"UpgradeSchemaFrom7To8",
js::ProfileEntry::Category::STORAGE);
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE object_store_index;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE temp_upgrade;"
));
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()
{ }
NS_IMETHOD
OnFunctionCall(mozIStorageValueArray* aArguments,
nsIVariant** aResult) override
{
MOZ_ASSERT(aArguments);
MOZ_ASSERT(aResult);
PROFILER_LABEL("IndexedDB",
"CompressDataBlobsFunction::OnFunctionCall",
js::ProfileEntry::Category::STORAGE);
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();
MOZ_ASSERT(aConnection);
PROFILER_LABEL("IndexedDB",
"UpgradeSchemaFrom8To9_0",
js::ProfileEntry::Category::STORAGE);
// We no longer use the dataVersion column.
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE database SET dataVersion = 0;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<mozIStorageFunction> compressor = new CompressDataBlobsFunction();
NS_NAMED_LITERAL_CSTRING(compressorName, "compress");
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(NS_LITERAL_CSTRING(
"UPDATE object_data SET data = compress(data);"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE ai_object_data SET data = compress(data);"
));
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();
MOZ_ASSERT(aConnection);
PROFILER_LABEL("IndexedDB",
"UpgradeSchemaFrom9_0To10_0",
js::ProfileEntry::Category::STORAGE);
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE object_data ADD COLUMN file_ids TEXT;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE ai_object_data ADD COLUMN file_ids TEXT;"
));
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();
MOZ_ASSERT(aConnection);
PROFILER_LABEL("IndexedDB",
"UpgradeSchemaFrom10_0To11_0",
js::ProfileEntry::Category::STORAGE);
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE object_store_index;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE temp_upgrade;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TRIGGER object_data_insert_trigger;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"DROP TABLE ai_unique_index_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE ai_index_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE ai_object_data;"
));
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()
{ }
NS_IMETHOD
OnFunctionCall(mozIStorageValueArray* aArguments,
nsIVariant** aResult) override
{
MOZ_ASSERT(aArguments);
MOZ_ASSERT(aResult);
PROFILER_LABEL("IndexedDB",
"EncodeKeysFunction::OnFunctionCall",
js::ProfileEntry::Category::STORAGE);
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;
}
Key key;
if (type == mozIStorageStatement::VALUE_TYPE_INTEGER) {
int64_t intKey;
aArguments->GetInt64(0, &intKey);
key.SetFromInteger(intKey);
} else if (type == mozIStorageStatement::VALUE_TYPE_TEXT) {
nsString stringKey;
aArguments->GetString(0, stringKey);
key.SetFromString(stringKey);
} else {
NS_WARNING("Don't call me with the wrong type of arguments!");
return 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();
MOZ_ASSERT(aConnection);
PROFILER_LABEL("IndexedDB",
"UpgradeSchemaFrom11_0To12_0",
js::ProfileEntry::Category::STORAGE);
NS_NAMED_LITERAL_CSTRING(encoderName, "encode");
nsCOMPtr<mozIStorageFunction> encoder = new EncodeKeysFunction();
nsresult rv = aConnection->CreateFunction(encoderName, 1, encoder);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TEMPORARY TABLE temp_upgrade ("
"id INTEGER PRIMARY KEY, "
"object_store_id, "
"key_value, "
"data, "
"file_ids "
");"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT INTO temp_upgrade "
"SELECT id, object_store_id, encode(key_value), data, file_ids "
"FROM object_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE object_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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"
");"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT INTO object_data "
"SELECT id, object_store_id, key_value, file_ids, data "
"FROM temp_upgrade;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE temp_upgrade;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"INSERT INTO temp_upgrade "
"SELECT index_id, encode(value), encode(object_data_key), object_data_id "
"FROM index_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE index_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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"
");"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"INSERT 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(NS_LITERAL_CSTRING(
"DROP TABLE temp_upgrade;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX index_data_object_data_id_index "
"ON index_data (object_data_id);"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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(NS_LITERAL_CSTRING(
"INSERT INTO temp_upgrade "
"SELECT index_id, encode(value), encode(object_data_key), object_data_id "
"FROM unique_index_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TABLE unique_index_data;"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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"
");"
));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"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;