diff --git a/dom/asmjscache/AsmJSCache.cpp b/dom/asmjscache/AsmJSCache.cpp index 1291d9e4b3..704496db05 100644 --- a/dom/asmjscache/AsmJSCache.cpp +++ b/dom/asmjscache/AsmJSCache.cpp @@ -833,13 +833,15 @@ MainProcessRunnable::OpenCacheFileForWrite() mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file); NS_ENSURE_STATE(mQuotaObject); - if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) { + if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize, + /* aTruncate */ false)) { // If the request fails, it might be because mOrigin is using too much - // space (MaybeAllocateMoreSpace will not evict our own origin since it is + // space (MaybeUpdateSize will not evict our own origin since it is // active). Try to make some space by evicting LRU entries until there is // enough space. EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata); - if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) { + if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize, + /* aTruncate */ false)) { mResult = JS::AsmJSCache_QuotaExceeded; return NS_ERROR_FAILURE; } diff --git a/dom/base/URL.cpp b/dom/base/URL.cpp index 9a0cd4a1e9..f86e7450ca 100644 --- a/dom/base/URL.cpp +++ b/dom/base/URL.cpp @@ -18,6 +18,7 @@ #include "nsNetCID.h" #include "nsNetUtil.h" #include "nsIURL.h" +#include "nsContentUtils.h" namespace mozilla { namespace dom { @@ -42,7 +43,7 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URL) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END -URL::URL(nsIURI* aURI) +URL::URL(already_AddRefed aURI) : mURI(aURI) { } @@ -57,56 +58,55 @@ URL::WrapObject(JSContext* aCx, JS::Handle aGivenProto, JS::MutableHa URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl, URL& aBase, ErrorResult& aRv) { - nsresult rv; - nsCOMPtr ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv)); - if (NS_FAILED(rv)) { - aRv.Throw(rv); - return nullptr; - } - - nsCOMPtr uri; - rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aUrl), nullptr, aBase.GetURI(), - getter_AddRefs(uri)); - if (NS_FAILED(rv)) { - nsAutoString label(aUrl); - aRv.ThrowTypeError(MSG_INVALID_URL, &label); - return nullptr; - } - - nsRefPtr url = new URL(uri); - return url.forget(); + return Constructor(aUrl, aBase.GetURI(), aRv); } /* static */ already_AddRefed URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl, - const nsAString& aBase, ErrorResult& aRv) + const Optional& aBase, ErrorResult& aRv) { - nsresult rv; - nsCOMPtr ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv)); - if (NS_FAILED(rv)) { - aRv.Throw(rv); - return nullptr; + if (aBase.WasPassed()) { + return Constructor(aUrl, aBase.Value(), aRv); } + return Constructor(aUrl, nullptr, aRv); +} + +/* static */ already_AddRefed +URL::Constructor(const GlobalObject& aGlobal, const nsAString& aUrl, + const nsAString& aBase, ErrorResult& aRv) +{ + return Constructor(aUrl, aBase, aRv); +} + +/* static */ already_AddRefed +URL::Constructor(const nsAString& aUrl, const nsAString& aBase, + ErrorResult& aRv) +{ nsCOMPtr baseUri; - rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aBase), nullptr, nullptr, - getter_AddRefs(baseUri)); - if (NS_FAILED(rv)) { - nsAutoString label(aBase); - aRv.ThrowTypeError(MSG_INVALID_URL, &label); + nsresult rv = NS_NewURI(getter_AddRefs(baseUri), aBase, nullptr, nullptr, + nsContentUtils::GetIOService()); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.ThrowTypeError(MSG_INVALID_URL, &aBase); return nullptr; } + return Constructor(aUrl, baseUri, aRv); +} + +/* static */ +already_AddRefed +URL::Constructor(const nsAString& aUrl, nsIURI* aBase, ErrorResult& aRv) +{ nsCOMPtr uri; - rv = ioService->NewURI(NS_ConvertUTF16toUTF8(aUrl), nullptr, baseUri, - getter_AddRefs(uri)); - if (NS_FAILED(rv)) { - nsAutoString label(aUrl); - aRv.ThrowTypeError(MSG_INVALID_URL, &label); + nsresult rv = NS_NewURI(getter_AddRefs(uri), aUrl, nullptr, aBase, + nsContentUtils::GetIOService()); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.ThrowTypeError(MSG_INVALID_URL, &aUrl); return nullptr; } - nsRefPtr url = new URL(uri); + nsRefPtr url = new URL(uri.forget()); return url.forget(); } diff --git a/dom/base/URL.h b/dom/base/URL.h index 378c8a1d61..107e74a98e 100644 --- a/dom/base/URL.h +++ b/dom/base/URL.h @@ -39,7 +39,7 @@ public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS(URL) - explicit URL(nsIURI* aURI); + explicit URL(already_AddRefed aURI); // WebIDL methods bool @@ -49,8 +49,16 @@ public: Constructor(const GlobalObject& aGlobal, const nsAString& aUrl, URL& aBase, ErrorResult& aRv); static already_AddRefed + Constructor(const GlobalObject& aGlobal, const nsAString& aUrl, + const Optional& aBase, ErrorResult& aRv); + // Versions of Constructor that we can share with workers and other code. + static already_AddRefed Constructor(const GlobalObject& aGlobal, const nsAString& aUrl, const nsAString& aBase, ErrorResult& aRv); + static already_AddRefed + Constructor(const nsAString& aUrl, const nsAString& aBase, ErrorResult& aRv); + static already_AddRefed + Constructor(const nsAString& aUrl, nsIURI* aBase, ErrorResult& aRv); static void CreateObjectURL(const GlobalObject& aGlobal, File& aBlob, diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index e323b85a5e..e56028e802 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -145,7 +145,7 @@ ErrorResult::ThrowErrorWithMessage(va_list ap, const dom::ErrNum errorNumber, MOZ_ASSERT(argCount <= 10); argCount = std::min(argCount, 10); while (argCount--) { - message->mArgs.AppendElement(*va_arg(ap, nsString*)); + message->mArgs.AppendElement(*va_arg(ap, const nsAString*)); } mMessage = message; #ifdef DEBUG diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 16e739fef8..b13430568f 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -1042,7 +1042,7 @@ WrapNewBindingNonWrapperCachedObject(JSContext* cx, // We can end up here in all sorts of compartments, per above. Make // sure to JS_WrapValue! rval.set(JS::ObjectValue(*obj)); - return JS_WrapValue(cx, rval); + return MaybeWrapObjectValue(cx, rval); } // Create a JSObject wrapping "value", for cases when "value" is a @@ -1090,7 +1090,7 @@ WrapNewBindingNonWrapperCachedObject(JSContext* cx, // We can end up here in all sorts of compartments, per above. Make // sure to JS_WrapValue! rval.set(JS::ObjectValue(*obj)); - return JS_WrapValue(cx, rval); + return MaybeWrapObjectValue(cx, rval); } // Helper for smart pointers (nsRefPtr/nsCOMPtr). diff --git a/dom/datastore/DataStoreDB.cpp b/dom/datastore/DataStoreDB.cpp index 58e936bde8..fff2e177b3 100644 --- a/dom/datastore/DataStoreDB.cpp +++ b/dom/datastore/DataStoreDB.cpp @@ -315,9 +315,13 @@ DataStoreDB::DatabaseOpened() return rv; } - nsRefPtr txn = mDatabase->Transaction(mObjectStores, - mTransactionMode, - error); + StringOrStringSequence objectStores; + objectStores.RawSetAsStringSequence().AppendElements(mObjectStores); + + nsRefPtr txn; + error = mDatabase->Transaction(objectStores, + mTransactionMode, + getter_AddRefs(txn)); if (NS_WARN_IF(error.Failed())) { return error.StealNSResult(); } diff --git a/dom/html/HTMLCanvasElement.cpp b/dom/html/HTMLCanvasElement.cpp index 3e525e7f60..c7e6a70402 100644 --- a/dom/html/HTMLCanvasElement.cpp +++ b/dom/html/HTMLCanvasElement.cpp @@ -563,8 +563,9 @@ HTMLCanvasElement::ToBlob(JSContext* aCx, nsresult rv = blob->GetSize(&size); if (NS_SUCCEEDED(rv)) { AutoJSAPI jsapi; - jsapi.Init(mGlobal); - JS_updateMallocCounter(jsapi.cx(), size); + if (jsapi.Init(mGlobal)) { + JS_updateMallocCounter(jsapi.cx(), size); + } } nsRefPtr newBlob = new File(mGlobal, blob->Impl()); diff --git a/dom/indexedDB/ActorsParent.cpp b/dom/indexedDB/ActorsParent.cpp index 2f34033bc8..950739bc02 100644 --- a/dom/indexedDB/ActorsParent.cpp +++ b/dom/indexedDB/ActorsParent.cpp @@ -1,5 +1,3 @@ -/* -*- 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/. */ @@ -53,6 +51,7 @@ #include "mozilla/ipc/InputStreamParams.h" #include "mozilla/ipc/InputStreamUtils.h" #include "mozilla/ipc/PBackground.h" +#include "mozilla/storage/Variant.h" #include "nsCharSeparatedTokenizer.h" #include "nsClassHashtable.h" #include "nsCOMPtr.h" @@ -64,7 +63,6 @@ #include "nsIEventTarget.h" #include "nsIFile.h" #include "nsIFileURL.h" -#include "nsIFileProtocolHandler.h" #include "nsIInputStream.h" #include "nsIInterfaceRequestor.h" #include "nsInterfaceHashtable.h" @@ -77,6 +75,8 @@ #include "nsISupports.h" #include "nsISupportsImpl.h" #include "nsISupportsPriority.h" +#include "nsIThread.h" +#include "nsITimer.h" #include "nsIURI.h" #include "nsNetUtil.h" #include "nsPrintfCString.h" @@ -88,14 +88,6 @@ #include "ProfilerHelpers.h" #include "ReportInternalError.h" #include "snappy/snappy.h" -#include "TransactionThreadPool.h" - -namespace mozilla { -namespace dom { -namespace indexedDB { - -using namespace mozilla::dom::quota; -using namespace mozilla::ipc; #define DISABLE_ASSERTS_FOR_FUZZING 0 @@ -105,8 +97,25 @@ using namespace mozilla::ipc; #define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__) #endif +#define IDB_DEBUG_LOG(_args) \ + PR_LOG(IndexedDatabaseManager::GetLoggingModule(), \ + PR_LOG_DEBUG, \ + _args ) + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) +#define IDB_MOBILE +#endif + +namespace mozilla { +namespace dom { +namespace indexedDB { + +using namespace mozilla::dom::quota; +using namespace mozilla::ipc; + namespace { +class ConnectionPool; class Cursor; class Database; struct DatabaseActorInfo; @@ -116,6 +125,7 @@ class DatabaseOfflineStorage; class Factory; class OpenDatabaseOp; class TransactionBase; +class TransactionDatabaseOperationBase; class VersionChangeTransaction; /******************************************************************************* @@ -128,7 +138,7 @@ static_assert(JS_STRUCTURED_CLONE_VERSION == 5, "Need to update the major schema version."); // Major schema version. Bump for almost everything. -const uint32_t kMajorSchemaVersion = 17; +const uint32_t kMajorSchemaVersion = 18; // Minor schema version. Should almost always be 0 (maybe bump on release // branches if we have to). @@ -147,6 +157,58 @@ const int32_t kSQLiteSchemaVersion = 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. +const uint32_t kConnectionIdleCheckpointsMS = 2 * 1000; // 2 seconds + +// The length of time that database connections will be held open after all +// transactions and checkpointing 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 + const char kSavepointClause[] = "SAVEPOINT sp;"; const uint32_t kFileCopyBufferSize = 32768; @@ -154,6 +216,9 @@ const uint32_t kFileCopyBufferSize = 32768; const char kJournalDirectoryName[] = "journals"; const char kFileManagerDirectoryNameSuffix[] = ".files"; +const char kSQLiteJournalSuffix[] = ".sqlite-journal"; +const char kSQLiteSHMSuffix[] = ".sqlite-shm"; +const char kSQLiteWALSuffix[] = ".sqlite-wal"; const char kPrefIndexedDBEnabled[] = "dom.indexedDB.enabled"; @@ -179,8 +244,34 @@ enum AppId { 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 +struct FreeDeleter +{ + void + operator()(void* aPtr) const + { + free(aPtr); + } +}; + +template +using UniqueFreePtr = UniquePtr; + +template +MOZ_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 ******************************************************************************/ @@ -235,6 +326,9 @@ public: NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata); + bool + HasLiveIndexes() const; + private: ~FullObjectStoreMetadata() { } @@ -351,6 +445,63 @@ private: } }; +struct IndexDataValue final +{ + int64_t mIndexId; + Key mKey; + bool mUnique; + + IndexDataValue() + : mIndexId(0) + , mUnique(false) + { + MOZ_COUNT_CTOR(IndexDataValue); + } + + explicit + IndexDataValue(const IndexDataValue& aOther) + : mIndexId(aOther.mIndexId) + , mKey(aOther.mKey) + , 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() + { + MOZ_COUNT_DTOR(IndexDataValue); + } + + bool + operator==(const IndexDataValue& aOther) const + { + return mIndexId == aOther.mIndexId && + mKey == aOther.mKey; + } + + bool + operator<(const IndexDataValue& aOther) const + { + if (mIndexId == aOther.mIndexId) { + return mKey < aOther.mKey; + } + + return mIndexId < aOther.mIndexId; + } +}; + /******************************************************************************* * SQLite functions ******************************************************************************/ @@ -444,6 +595,335 @@ GetDatabaseFilename(const nsAString& aName, aDatabaseFilename.AppendASCII(substring.get(), substring.Length()); } +uint32_t +CompressedByteCountForNumber(uint64_t aNumber) +{ + MOZ_ASSERT(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& aIndexValues, + UniqueFreePtr& 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 uint32_t keyBufferLength = keyBuffer.Length(); + + MOZ_ASSERT(!keyBuffer.IsEmpty()); + + // Don't let |infoLength| overflow. + if (NS_WARN_IF(UINT32_MAX - keyBuffer.Length() < + CompressedByteCountForIndexId(info.mIndexId) + + CompressedByteCountForNumber(keyBufferLength))) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + const uint32_t infoLength = + CompressedByteCountForIndexId(info.mIndexId) + + CompressedByteCountForNumber(keyBufferLength) + + keyBufferLength; + + // Don't let |blobDataLength| overflow. + if (NS_WARN_IF(UINT32_MAX - infoLength < blobDataLength)) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; + } + + blobDataLength += infoLength; + } + + UniqueFreePtr blobData( + static_cast(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 uint32_t keyBufferLength = keyBuffer.Length(); + + WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter); + WriteCompressedNumber(keyBuffer.Length(), &blobDataIter); + + memcpy(blobDataIter, keyBuffer.get(), keyBufferLength); + blobDataIter += keyBufferLength; + } + + 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, + FallibleTArray& 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); + + 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(blobDataIter + keyBufferLength > blobDataEnd)) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_FILE_CORRUPTED; + } + + nsCString keyBuffer(reinterpret_cast(blobDataIter), + uint32_t(keyBufferLength)); + blobDataIter += keyBufferLength; + + if (NS_WARN_IF(!aIndexValues.InsertElementSorted( + IndexDataValue(indexId, unique, Key(keyBuffer))))) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_OUT_OF_MEMORY; + } + } + + MOZ_ASSERT(blobDataIter == blobDataEnd); + + return NS_OK; +} + +// static +template +nsresult +ReadCompressedIndexDataValuesFromSource( + T* aSource, + uint32_t aColumnIndex, + FallibleTArray& 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, + FallibleTArray& aIndexValues) +{ + return ReadCompressedIndexDataValuesFromSource(aStatement, + aColumnIndex, + aIndexValues); +} + +nsresult +ReadCompressedIndexDataValues(mozIStorageValueArray* aValues, + uint32_t aColumnIndex, + FallibleTArray& aIndexValues) +{ + return ReadCompressedIndexDataValuesFromSource(aValues, + aColumnIndex, + aIndexValues); +} + nsresult CreateFileTables(mozIStorageConnection* aConnection) { @@ -530,10 +1010,14 @@ CreateTables(mozIStorageConnection* aConnection) // Table `database` nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE TABLE database (" - "name TEXT NOT NULL, " - "version INTEGER NOT NULL DEFAULT 0" - ");" + "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; @@ -541,30 +1025,12 @@ CreateTables(mozIStorageConnection* aConnection) // 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, " - "UNIQUE (name)" - ");" - )); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - // Table `object_data` - 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" - ");" + "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; @@ -572,17 +1038,33 @@ CreateTables(mozIStorageConnection* aConnection) // Table `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, " - "UNIQUE (object_store_id, name), " - "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " - "CASCADE" - ");" + "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" + ", 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; @@ -590,26 +1072,17 @@ CreateTables(mozIStorageConnection* aConnection) // 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_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; - } - - // Need this to make cascading deletes from object_data and object_store fast. - rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE INDEX index_data_object_data_id_index " - "ON index_data (object_data_id);" + "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" + ", 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; @@ -617,27 +1090,17 @@ CreateTables(mozIStorageConnection* aConnection) // 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_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; - } - - // Need this to make cascading deletes from object_data and object_store fast. - rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( - "CREATE INDEX unique_index_data_object_data_id_index " - "ON unique_index_data (object_data_id);" + "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" + ", 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; @@ -2066,7 +2529,7 @@ UpgradeSchemaFrom12_0To13_0(mozIStorageConnection* aConnection, nsresult rv; -#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) +#ifdef IDB_MOBILE int32_t defaultPageSize; rv = aConnection->GetDefaultPageSize(&defaultPageSize); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -2148,6 +2611,925 @@ UpgradeSchemaFrom16_0To17_0(mozIStorageConnection* aConnection) return NS_OK; } +class UpgradeSchemaFrom17_0To18_0Helper final +{ + class InsertIndexDataValuesFunction; + class UpgradeKeyFunction; + +public: + static nsresult + DoUpgrade(mozIStorageConnection* aConnection, const nsACString& aOrigin); + +private: + static nsresult + DoUpgradeInternal(mozIStorageConnection* aConnection, + const nsACString& aOrigin); + + UpgradeSchemaFrom17_0To18_0Helper() + { + MOZ_ASSERT_UNREACHABLE("Don't create instances of this class!"); + } + + ~UpgradeSchemaFrom17_0To18_0Helper() + { + MOZ_ASSERT_UNREACHABLE("Don't create instances of this class!"); + } +}; + +class UpgradeSchemaFrom17_0To18_0Helper::InsertIndexDataValuesFunction final + : public mozIStorageFunction +{ +public: + InsertIndexDataValuesFunction() + { } + + NS_DECL_ISUPPORTS + +private: + ~InsertIndexDataValuesFunction() + { } + + NS_DECL_MOZISTORAGEFUNCTION +}; + +NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper:: + InsertIndexDataValuesFunction, + mozIStorageFunction); + +NS_IMETHODIMP +UpgradeSchemaFrom17_0To18_0Helper:: +InsertIndexDataValuesFunction::OnFunctionCall(mozIStorageValueArray* aValues, + nsIVariant** _retval) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(!IsOnBackgroundThread()); + MOZ_ASSERT(aValues); + MOZ_ASSERT(_retval); + +#ifdef DEBUG + { + uint32_t argCount; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetNumEntries(&argCount))); + MOZ_ASSERT(argCount == 4); + + int32_t valueType; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(0, &valueType))); + MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL || + valueType == mozIStorageValueArray::VALUE_TYPE_BLOB); + + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(1, &valueType))); + MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER); + + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(2, &valueType))); + MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER); + + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(3, &valueType))); + MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB); + } +#endif + + // Read out the previous value. It may be NULL, in which case we'll just end + // up with an empty array. + AutoFallibleTArray indexValues; + nsresult rv = ReadCompressedIndexDataValues(aValues, 0, indexValues); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int64_t indexId; + rv = aValues->GetInt64(1, &indexId); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + int32_t unique; + rv = aValues->GetInt32(2, &unique); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + Key value; + rv = value.SetFromValueArray(aValues, 3); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Update the array with the new addition. + if (NS_WARN_IF(!indexValues.SetCapacity(indexValues.Length() + 1))) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_OUT_OF_MEMORY; + } + + MOZ_ALWAYS_TRUE( + indexValues.InsertElementSorted(IndexDataValue(indexId, !!unique, value))); + + // Compress the array. + UniqueFreePtr indexValuesBlob; + uint32_t indexValuesBlobLength; + rv = MakeCompressedIndexDataValues(indexValues, + indexValuesBlob, + &indexValuesBlobLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // The compressed blob is the result of this function. + std::pair indexValuesBlobPair(indexValuesBlob.release(), + indexValuesBlobLength); + + nsCOMPtr result = + new storage::AdoptedBlobVariant(indexValuesBlobPair); + + result.forget(_retval); + return NS_OK; +} + +class UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction final + : public mozIStorageFunction +{ +public: + UpgradeKeyFunction() + { } + + static nsresult + CopyAndUpgradeKeyBuffer(const uint8_t* aSource, + const uint8_t* aSourceEnd, + uint8_t* aDestination) + { + return CopyAndUpgradeKeyBufferInternal(aSource, + aSourceEnd, + aDestination, + 0 /* aTagOffset */, + 0 /* aRecursionDepth */); + } + + NS_DECL_ISUPPORTS + +private: + ~UpgradeKeyFunction() + { } + + static nsresult + CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource, + const uint8_t* aSourceEnd, + uint8_t*& aDestination, + uint8_t aTagOffset, + uint8_t aRecursionDepth); + + static uint32_t + AdjustedSize(uint32_t aMaxSize, + const uint8_t* aSource, + const uint8_t* aSourceEnd) + { + MOZ_ASSERT(aMaxSize); + MOZ_ASSERT(aSource); + MOZ_ASSERT(aSourceEnd); + MOZ_ASSERT(aSource <= aSourceEnd); + + return std::min(aMaxSize, uint32_t(aSourceEnd - aSource)); + } + + NS_DECL_MOZISTORAGEFUNCTION +}; + +// static +nsresult +UpgradeSchemaFrom17_0To18_0Helper:: +UpgradeKeyFunction::CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource, + const uint8_t* aSourceEnd, + uint8_t*& aDestination, + uint8_t aTagOffset, + uint8_t aRecursionDepth) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(!IsOnBackgroundThread()); + MOZ_ASSERT(aSource); + MOZ_ASSERT(*aSource); + MOZ_ASSERT(aSourceEnd); + MOZ_ASSERT(aSource < aSourceEnd); + MOZ_ASSERT(aDestination); + MOZ_ASSERT(aTagOffset <= Key::kMaxArrayCollapse); + + static MOZ_CONSTEXPR_VAR uint8_t kOldNumberTag = 0x1; + static MOZ_CONSTEXPR_VAR uint8_t kOldDateTag = 0x2; + static MOZ_CONSTEXPR_VAR uint8_t kOldStringTag = 0x3; + static MOZ_CONSTEXPR_VAR uint8_t kOldArrayTag = 0x4; + static MOZ_CONSTEXPR_VAR uint8_t kOldMaxType = kOldArrayTag; + + if (NS_WARN_IF(aRecursionDepth > Key::kMaxRecursionDepth)) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_FILE_CORRUPTED; + } + + const uint8_t sourceTag = *aSource - (aTagOffset * kOldMaxType); + MOZ_ASSERT(sourceTag); + + if (NS_WARN_IF(sourceTag > kOldMaxType * Key::kMaxArrayCollapse)) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_FILE_CORRUPTED; + } + + if (sourceTag == kOldNumberTag || sourceTag == kOldDateTag) { + // Write the new tag. + *aDestination++ = + (sourceTag == kOldNumberTag ? Key::eFloat : Key::eDate) + + (aTagOffset * Key::eMaxType); + aSource++; + + // Numbers and Dates are encoded as 64-bit integers, but trailing 0 + // bytes have been removed. + const uint32_t byteCount = + AdjustedSize(sizeof(uint64_t), aSource, aSourceEnd); + + for (uint32_t count = 0; count < byteCount; count++) { + *aDestination++ = *aSource++; + } + + return NS_OK; + } + + if (sourceTag == kOldStringTag) { + // Write the new tag. + *aDestination++ = Key::eString + (aTagOffset * Key::eMaxType); + aSource++; + + while (aSource < aSourceEnd) { + const uint8_t byte = *aSource++; + *aDestination++ = byte; + + if (!byte) { + // Just copied the terminator. + break; + } + + // Maybe copy one or two extra bytes if the byte is tagged and we have + // enough source space. + if (byte & 0x80) { + const uint32_t byteCount = + AdjustedSize((byte & 0x40) ? 2 : 1, aSource, aSourceEnd); + + for (uint32_t count = 0; count < byteCount; count++) { + *aDestination++ = *aSource++; + } + } + } + + return NS_OK; + } + + if (NS_WARN_IF(sourceTag < kOldArrayTag)) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_FILE_CORRUPTED; + } + + aTagOffset++; + + if (aTagOffset == Key::kMaxArrayCollapse) { + MOZ_ASSERT(sourceTag == kOldArrayTag); + + *aDestination++ = (aTagOffset * Key::eMaxType); + aSource++; + + aTagOffset = 0; + } + + while (aSource < aSourceEnd && + (*aSource - (aTagOffset * kOldMaxType)) != Key::eTerminator) { + nsresult rv = CopyAndUpgradeKeyBufferInternal(aSource, + aSourceEnd, + aDestination, + aTagOffset, + aRecursionDepth + 1); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + aTagOffset = 0; + } + + if (aSource < aSourceEnd) { + MOZ_ASSERT((*aSource - (aTagOffset * kOldMaxType)) == Key::eTerminator); + *aDestination++ = Key::eTerminator + (aTagOffset * Key::eMaxType); + aSource++; + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction, + mozIStorageFunction); + +NS_IMETHODIMP +UpgradeSchemaFrom17_0To18_0Helper:: +UpgradeKeyFunction::OnFunctionCall(mozIStorageValueArray* aValues, + nsIVariant** _retval) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(!IsOnBackgroundThread()); + MOZ_ASSERT(aValues); + MOZ_ASSERT(_retval); + +#ifdef DEBUG + { + uint32_t argCount; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetNumEntries(&argCount))); + MOZ_ASSERT(argCount == 1); + + int32_t valueType; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(0, &valueType))); + MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB); + } +#endif + + // Dig the old key out of the values. + const uint8_t* blobData; + uint32_t blobDataLength; + nsresult rv = aValues->GetSharedBlob(0, &blobDataLength, &blobData); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Upgrading the key doesn't change the amount of space needed to hold it. + UniqueFreePtr upgradedBlobData( + static_cast(malloc(blobDataLength))); + if (NS_WARN_IF(!upgradedBlobData)) { + IDB_REPORT_INTERNAL_ERR(); + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = CopyAndUpgradeKeyBuffer(blobData, + blobData + blobDataLength, + upgradedBlobData.get()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // The upgraded key is the result of this function. + std::pair data(upgradedBlobData.release(), + int(blobDataLength)); + + nsCOMPtr result = new mozilla::storage::AdoptedBlobVariant(data); + + upgradedBlobData.release(); + + result.forget(_retval); + return NS_OK; +} + +// static +nsresult +UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(mozIStorageConnection* aConnection, + const nsACString& aOrigin) +{ + MOZ_ASSERT(aConnection); + MOZ_ASSERT(!aOrigin.IsEmpty()); + + // Register the |upgrade_key| function. + nsRefPtr updateFunction = new UpgradeKeyFunction(); + + NS_NAMED_LITERAL_CSTRING(upgradeKeyFunctionName, "upgrade_key"); + + nsresult rv = + aConnection->CreateFunction(upgradeKeyFunctionName, 1, updateFunction); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Register the |insert_idv| function. + nsRefPtr insertIDVFunction = + new InsertIndexDataValuesFunction(); + + NS_NAMED_LITERAL_CSTRING(insertIDVFunctionName, "insert_idv"); + + rv = aConnection->CreateFunction(insertIDVFunctionName, 4, insertIDVFunction); + if (NS_WARN_IF(NS_FAILED(rv))) { + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + aConnection->RemoveFunction(upgradeKeyFunctionName))); + return rv; + } + + rv = DoUpgradeInternal(aConnection, aOrigin); + + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + aConnection->RemoveFunction(upgradeKeyFunctionName))); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( + aConnection->RemoveFunction(insertIDVFunctionName))); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +// static +nsresult +UpgradeSchemaFrom17_0To18_0Helper::DoUpgradeInternal( + mozIStorageConnection* aConnection, + const nsACString& aOrigin) +{ + MOZ_ASSERT(aConnection); + MOZ_ASSERT(!aOrigin.IsEmpty()); + + nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // Drop these triggers to avoid unnecessary work during the upgrade process. + "DROP TRIGGER object_data_insert_trigger;" + "DROP TRIGGER object_data_update_trigger;" + "DROP TRIGGER object_data_delete_trigger;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // Drop these indexes before we do anything else to free disk space. + "DROP INDEX index_data_object_data_id_index;" + "DROP INDEX unique_index_data_object_data_id_index;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Create the new tables and triggers first. + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // This will eventually become the |database| table. + "CREATE TABLE database_upgrade " + "( name TEXT PRIMARY KEY" + ", origin TEXT NOT NULL" + ", version INTEGER NOT NULL DEFAULT 0" + ", last_vacuum_time INTEGER NOT NULL DEFAULT 0" + ", last_analyze_time INTEGER NOT NULL DEFAULT 0" + ", last_vacuum_size INTEGER NOT NULL DEFAULT 0" + ") WITHOUT ROWID;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // This will eventually become the |object_store| table. + "CREATE TABLE object_store_upgrade" + "( id INTEGER PRIMARY KEY" + ", auto_increment INTEGER NOT NULL DEFAULT 0" + ", name TEXT NOT NULL" + ", key_path TEXT" + ");" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // This will eventually become the |object_store_index| table. + "CREATE TABLE object_store_index_upgrade" + "( id INTEGER PRIMARY KEY" + ", object_store_id INTEGER NOT NULL" + ", name TEXT NOT NULL" + ", key_path TEXT NOT NULL" + ", unique_index INTEGER NOT NULL" + ", multientry INTEGER NOT NULL" + ", FOREIGN KEY (object_store_id) " + "REFERENCES object_store(id) " + ");" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // This will eventually become the |object_data| table. + "CREATE TABLE object_data_upgrade" + "( object_store_id INTEGER NOT NULL" + ", key BLOB NOT NULL" + ", index_data_values BLOB DEFAULT NULL" + ", file_ids TEXT" + ", data BLOB NOT NULL" + ", PRIMARY KEY (object_store_id, key)" + ", FOREIGN KEY (object_store_id) " + "REFERENCES object_store(id) " + ") WITHOUT ROWID;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // This will eventually become the |index_data| table. + "CREATE TABLE index_data_upgrade" + "( index_id INTEGER NOT NULL" + ", value BLOB NOT NULL" + ", object_data_key BLOB NOT NULL" + ", object_store_id INTEGER NOT NULL" + ", PRIMARY KEY (index_id, value, object_data_key)" + ", FOREIGN KEY (index_id) " + "REFERENCES object_store_index(id) " + ", FOREIGN KEY (object_store_id, object_data_key) " + "REFERENCES object_data(object_store_id, key) " + ") WITHOUT ROWID;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // This will eventually become the |unique_index_data| table. + "CREATE TABLE unique_index_data_upgrade" + "( index_id INTEGER NOT NULL" + ", value BLOB NOT NULL" + ", object_store_id INTEGER NOT NULL" + ", object_data_key BLOB NOT NULL" + ", PRIMARY KEY (index_id, value)" + ", FOREIGN KEY (index_id) " + "REFERENCES object_store_index(id) " + ", FOREIGN KEY (object_store_id, object_data_key) " + "REFERENCES object_data(object_store_id, key) " + ") WITHOUT ROWID;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // Temporarily store |index_data_values| that we build during the upgrade of + // the index tables. We will later move this to the |object_data| table. + "CREATE TEMPORARY TABLE temp_index_data_values " + "( object_store_id INTEGER NOT NULL" + ", key BLOB NOT NULL" + ", index_data_values BLOB DEFAULT NULL" + ", PRIMARY KEY (object_store_id, key)" + ") WITHOUT ROWID;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // These two triggers help build the |index_data_values| blobs. The nested + // SELECT statements help us achieve an "INSERT OR UPDATE"-like behavior. + "CREATE TEMPORARY TRIGGER unique_index_data_upgrade_insert_trigger " + "AFTER INSERT ON unique_index_data_upgrade " + "BEGIN " + "INSERT OR REPLACE INTO temp_index_data_values " + "VALUES " + "( NEW.object_store_id" + ", NEW.object_data_key" + ", insert_idv(" + "( SELECT index_data_values " + "FROM temp_index_data_values " + "WHERE object_store_id = NEW.object_store_id " + "AND key = NEW.object_data_key " + "), NEW.index_id" + ", 1" /* unique */ + ", NEW.value" + ")" + ");" + "END;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TEMPORARY TRIGGER index_data_upgrade_insert_trigger " + "AFTER INSERT ON index_data_upgrade " + "BEGIN " + "INSERT OR REPLACE INTO temp_index_data_values " + "VALUES " + "( NEW.object_store_id" + ", NEW.object_data_key" + ", insert_idv(" + "(" + "SELECT index_data_values " + "FROM temp_index_data_values " + "WHERE object_store_id = NEW.object_store_id " + "AND key = NEW.object_data_key " + "), NEW.index_id" + ", 0" /* not unique */ + ", NEW.value" + ")" + ");" + "END;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Update the |unique_index_data| table to change the column order, remove the + // ON DELETE CASCADE clauses, and to apply the WITHOUT ROWID optimization. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // Insert all the data. + "INSERT INTO unique_index_data_upgrade " + "SELECT " + "unique_index_data.index_id, " + "upgrade_key(unique_index_data.value), " + "object_data.object_store_id, " + "upgrade_key(unique_index_data.object_data_key) " + "FROM unique_index_data " + "JOIN object_data " + "ON unique_index_data.object_data_id = object_data.id;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // The trigger is no longer needed. + "DROP TRIGGER unique_index_data_upgrade_insert_trigger;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // The old table is no longer needed. + "DROP TABLE unique_index_data;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // Rename the table. + "ALTER TABLE unique_index_data_upgrade " + "RENAME TO unique_index_data;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Update the |index_data| table to change the column order, remove the ON + // DELETE CASCADE clauses, and to apply the WITHOUT ROWID optimization. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // Insert all the data. + "INSERT INTO index_data_upgrade " + "SELECT " + "index_data.index_id, " + "upgrade_key(index_data.value), " + "upgrade_key(index_data.object_data_key), " + "object_data.object_store_id " + "FROM index_data " + "JOIN object_data " + "ON index_data.object_data_id = object_data.id;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // The trigger is no longer needed. + "DROP TRIGGER index_data_upgrade_insert_trigger;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // The old table is no longer needed. + "DROP TABLE index_data;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // Rename the table. + "ALTER TABLE index_data_upgrade " + "RENAME TO index_data;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Update the |object_data| table to add the |index_data_values| column, + // remove the ON DELETE CASCADE clause, and apply the WITHOUT ROWID + // optimization. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // Insert all the data. + "INSERT INTO object_data_upgrade " + "SELECT " + "object_data.object_store_id, " + "upgrade_key(object_data.key_value), " + "temp_index_data_values.index_data_values, " + "object_data.file_ids, " + "object_data.data " + "FROM object_data " + "LEFT JOIN temp_index_data_values " + "ON object_data.object_store_id = " + "temp_index_data_values.object_store_id " + "AND upgrade_key(object_data.key_value) = " + "temp_index_data_values.key;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // The temporary table is no longer needed. + "DROP TABLE temp_index_data_values;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // The old table is no longer needed. + "DROP TABLE object_data;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + // Rename the table. + "ALTER TABLE object_data_upgrade " + "RENAME TO object_data;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Update the |object_store_index| table to remove the UNIQUE constraint and + // the ON DELETE CASCADE clause. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT INTO object_store_index_upgrade " + "SELECT * " + "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( + "ALTER TABLE object_store_index_upgrade " + "RENAME TO object_store_index;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Update the |object_store| table to remove the UNIQUE constraint. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "INSERT INTO object_store_upgrade " + "SELECT * " + "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( + "ALTER TABLE object_store_upgrade " + "RENAME TO object_store;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Update the |database| table to include the origin, vacuum information, and + // apply the WITHOUT ROWID optimization. + nsCOMPtr stmt; + rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( + "INSERT INTO database_upgrade " + "SELECT name, :origin, version, 0, 0, 0 " + "FROM database;" + ), getter_AddRefs(stmt)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), aOrigin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = stmt->Execute(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "DROP TABLE database;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "ALTER TABLE database_upgrade " + "RENAME TO database;" + )); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifdef DEBUG + { + // Make sure there's only one entry in the |database| table. + nsCOMPtr stmt; + MOZ_ASSERT(NS_SUCCEEDED( + aConnection->CreateStatement( + NS_LITERAL_CSTRING("SELECT COUNT(*) " + "FROM database;"), + getter_AddRefs(stmt)))); + + bool hasResult; + MOZ_ASSERT(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult))); + + int64_t count; + MOZ_ASSERT(NS_SUCCEEDED(stmt->GetInt64(0, &count))); + + MOZ_ASSERT(count == 1); + } +#endif + + // Recreate file table triggers. + rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( + "CREATE TRIGGER object_data_insert_trigger " + "AFTER INSERT ON object_data " + "WHEN NEW.file_ids IS NOT NULL " + "BEGIN " + "SELECT update_refcount(NULL, NEW.file_ids);" + "END;" + )); + 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 " + "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 " + "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; + } + + // Finally, turn on auto_vacuum mode. We use full auto_vacuum mode to reclaim + // disk space on mobile devices (at the cost of some COMMIT speed), and + // incremental auto_vacuum mode on desktop builds. + rv = aConnection->ExecuteSimpleSQL( +#ifdef IDB_MOBILE + NS_LITERAL_CSTRING("PRAGMA auto_vacuum = FULL;") +#else + NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL;") +#endif + ); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = aConnection->SetSchemaVersion(MakeSchemaVersion(18, 0)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +UpgradeSchemaFrom17_0To18_0(mozIStorageConnection* aConnection, + const nsACString& aOrigin) +{ + MOZ_ASSERT(aConnection); + MOZ_ASSERT(!aOrigin.IsEmpty()); + + PROFILER_LABEL("IndexedDB", + "UpgradeSchemaFrom17_0To18_0", + js::ProfileEntry::Category::STORAGE); + + return UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(aConnection, aOrigin); +} + nsresult GetDatabaseFileURL(nsIFile* aDatabaseFile, PersistenceType aPersistenceType, @@ -2158,22 +3540,8 @@ GetDatabaseFileURL(nsIFile* aDatabaseFile, MOZ_ASSERT(aDatabaseFile); MOZ_ASSERT(aResult); - nsresult rv; - - nsCOMPtr protocolHandler( - do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file", &rv)); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - - nsCOMPtr fileHandler( - do_QueryInterface(protocolHandler, &rv)); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - nsCOMPtr uri; - rv = fileHandler->NewFileURI(aDatabaseFile, getter_AddRefs(uri)); + nsresult rv = NS_NewFileURI(getter_AddRefs(uri), aDatabaseFile); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -2186,7 +3554,8 @@ GetDatabaseFileURL(nsIFile* aDatabaseFile, rv = fileUrl->SetQuery(NS_LITERAL_CSTRING("persistenceType=") + type + NS_LITERAL_CSTRING("&group=") + aGroup + - NS_LITERAL_CSTRING("&origin=") + aOrigin); + NS_LITERAL_CSTRING("&origin=") + aOrigin + + NS_LITERAL_CSTRING("&cache=private")); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } @@ -2198,54 +3567,243 @@ GetDatabaseFileURL(nsIFile* aDatabaseFile, nsresult SetDefaultPragmas(mozIStorageConnection* aConnection) { + MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConnection); - static const char query[] = -#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) - // Switch the journaling mode to TRUNCATE to avoid changing the directory - // structure at the conclusion of every transaction for devices with slower - // file systems. - "PRAGMA journal_mode = TRUNCATE; " + static const char kBuiltInPragmas[] = + // We use foreign keys in DEBUG builds only because there is a performance + // cost to using them. + "PRAGMA foreign_keys = " +#ifdef DEBUG + "ON" +#else + "OFF" #endif - // We use foreign keys in lots of places. - "PRAGMA foreign_keys = ON; " + ";" + // The "INSERT OR REPLACE" statement doesn't fire the update trigger, // instead it fires only the insert trigger. This confuses the update // refcount function. This behavior changes with enabled recursive triggers, // so the statement fires the delete trigger first and then the insert // trigger. "PRAGMA recursive_triggers = ON;" - // We don't need SQLite's table locks because we manage transaction ordering - // ourselves and we know we will never allow a write transaction to modify - // an object store that a read transaction is in the process of using. - "PRAGMA read_uncommitted = TRUE;" - // No more PRAGMAs. - ; - nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(query)); + // We aggressively truncate the database file when idle so don't bother + // overwriting the WAL with 0 during active periods. + "PRAGMA secure_delete = OFF;" + ; + + nsresult rv = + aConnection->ExecuteSimpleSQL( + nsDependentCString(kBuiltInPragmas, + LiteralStringLength(kBuiltInPragmas))); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } + nsAutoCString pragmaStmt; + pragmaStmt.AssignLiteral("PRAGMA synchronous = "); + if (IndexedDatabaseManager::FullSynchronous()) { - rv = aConnection->ExecuteSimpleSQL( - NS_LITERAL_CSTRING("PRAGMA synchronous = FULL;")); + pragmaStmt.AppendLiteral("FULL"); + } else { + pragmaStmt.AppendLiteral("NORMAL"); + } + pragmaStmt.Append(';'); + + rv = aConnection->ExecuteSimpleSQL(pragmaStmt); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifndef IDB_MOBILE + if (kSQLiteGrowthIncrement) { + rv = aConnection->SetGrowthIncrement(kSQLiteGrowthIncrement, + EmptyCString()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } +#endif // IDB_MOBILE return NS_OK; } nsresult -CreateDatabaseConnection(nsIFile* aDBFile, - nsIFile* aFMDirectory, - const nsAString& aName, - PersistenceType aPersistenceType, - const nsACString& aGroup, - const nsACString& aOrigin, - mozIStorageConnection** aConnection) +SetJournalMode(mozIStorageConnection* aConnection) +{ + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ASSERT(aConnection); + + // Try enabling WAL mode. This can fail in various circumstances so we have to + // check the results here. + NS_NAMED_LITERAL_CSTRING(journalModeQueryStart, "PRAGMA journal_mode = "); + NS_NAMED_LITERAL_CSTRING(journalModeWAL, "wal"); + + nsCOMPtr stmt; + nsresult rv = + aConnection->CreateStatement(journalModeQueryStart + journalModeWAL, + getter_AddRefs(stmt)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool hasResult; + rv = stmt->ExecuteStep(&hasResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(hasResult); + + nsCString journalMode; + rv = stmt->GetUTF8String(0, journalMode); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (journalMode.Equals(journalModeWAL)) { + // WAL mode successfully enabled. Maybe set limits on its size here. + if (kMaxWALPages >= 0) { + nsAutoCString pageCount; + pageCount.AppendInt(kMaxWALPages); + + rv = aConnection->ExecuteSimpleSQL( + NS_LITERAL_CSTRING("PRAGMA wal_autocheckpoint = ") + pageCount); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } else { + NS_WARNING("Failed to set WAL mode, falling back to normal journal mode."); +#ifdef IDB_MOBILE + rv = aConnection->ExecuteSimpleSQL(journalModeQueryStart + + NS_LITERAL_CSTRING("truncate")); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + } + + return NS_OK; +} + +template +struct StorageOpenTraits; + +template <> +struct StorageOpenTraits +{ + static nsresult + Open(mozIStorageService* aStorageService, + nsIFileURL* aFileURL, + mozIStorageConnection** aConnection) + { + return aStorageService->OpenDatabaseWithFileURL(aFileURL, aConnection); + } + +#ifdef DEBUG + static void + GetPath(nsIFileURL* aFileURL, nsCString& aPath) + { + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aFileURL->GetFileName(aPath))); + } +#endif +}; + +template <> +struct StorageOpenTraits +{ + static nsresult + Open(mozIStorageService* aStorageService, + nsIFile* aFile, + mozIStorageConnection** aConnection) + { + return aStorageService->OpenUnsharedDatabase(aFile, aConnection); + } + +#ifdef DEBUG + static void + GetPath(nsIFile* aFile, nsCString& aPath) + { + nsString path; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aFile->GetPath(path))); + + aPath.AssignWithConversion(path); + } +#endif +}; + +template