/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ActorsParent.h" #include #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/Endian.h" #include "mozilla/Hal.h" #include "mozilla/LazyIdleThread.h" #include "mozilla/Maybe.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "mozilla/StaticPtr.h" #include "mozilla/storage.h" #include "mozilla/unused.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FileService.h" #include "mozilla/dom/StructuredCloneTags.h" #include "mozilla/dom/TabParent.h" #include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h" #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h" #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.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/PIndexedDBPermissionRequestParent.h" #include "mozilla/dom/ipc/BlobParent.h" #include "mozilla/dom/quota/Client.h" #include "mozilla/dom/quota/FileStreams.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/storage/Variant.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 "nsIIdleService.h" #include "nsIInputStream.h" #include "nsIInterfaceRequestor.h" #include "nsInterfaceHashtable.h" #include "nsIObserver.h" #include "nsIObserverService.h" #include "nsIOutputStream.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 "nsRefPtrHashtable.h" #include "nsString.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) || 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; class DatabaseLoggingInfo; class DatabaseFile; class Factory; 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 == 5, "Need to update the major schema version."); // Major schema version. Bump for almost everything. const uint32_t kMajorSchemaVersion = 18; // 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 kPrefIndexedDBEnabled[] = "dom.indexedDB.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" const char kIdleServiceContractId[] = "@mozilla.org/widget/idleservice;1"; #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 const bool kRunningXPCShellTests = #ifdef ENABLE_TESTS !!PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR") #else false #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 ******************************************************************************/ struct FullIndexMetadata { IndexMetadata mCommonMetadata; bool mDeleted; public: FullIndexMetadata() : mCommonMetadata(0, nsString(), KeyPath(0), 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 IndexTable; struct FullObjectStoreMetadata { ObjectStoreMetadata mCommonMetadata; IndexTable mIndexes; // These two members are only ever touched on a transaction thread! int64_t mNextAutoIncrementId; int64_t mComittedAutoIncrementId; bool mDeleted; public: FullObjectStoreMetadata() : mCommonMetadata(0, nsString(), KeyPath(0), false) , mNextAutoIncrementId(0) , mComittedAutoIncrementId(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 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 Duplicate() const; private: ~FullDatabaseMetadata() { } }; template class MOZ_STACK_CLASS MetadataNameOrIdMatcher final { typedef MetadataNameOrIdMatcher SelfType; const int64_t mId; const nsString mName; nsRefPtr mMetadata; bool mCheckName; public: template static MetadataType* Match(const Enumerable& aEnumerable, uint64_t aId, const nsAString& aName) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aId); SelfType closure(aId, aName); aEnumerable.EnumerateRead(Enumerate, &closure); return closure.mMetadata; } template static MetadataType* Match(const Enumerable& aEnumerable, uint64_t aId) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aId); SelfType closure(aId); aEnumerable.EnumerateRead(Enumerate, &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); } static PLDHashOperator Enumerate(const uint64_t& aKey, MetadataType* aValue, void* aClosure) { AssertIsOnBackgroundThread(); MOZ_ASSERT(aKey); MOZ_ASSERT(aValue); MOZ_ASSERT(aClosure); auto* closure = static_cast(aClosure); if (!aValue->mDeleted && (closure->mId == aValue->mCommonMetadata.id() || (closure->mCheckName && closure->mName == aValue->mCommonMetadata.name()))) { closure->mMetadata = aValue; return PL_DHASH_STOP; } return PL_DHASH_NEXT; } }; 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 ******************************************************************************/ 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) { 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)), fallible))) { 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) { 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` 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 `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" ", 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" ", 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; } // 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" ", 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 = 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 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); nsAutoArrayPtr compressed(new (fallible) char[compressedLength]); if (NS_WARN_IF(!compressed)) { return NS_ERROR_OUT_OF_MEMORY; } snappy::RawCompress(reinterpret_cast(uncompressed), uncompressedLength, compressed.get(), &compressedLength); std::pair data(static_cast(compressed.get()), int(compressedLength)); // XXX This copies the buffer again... There doesn't appear to be any way to // preallocate space and write directly to a BlobVariant at the moment. nsCOMPtr result = new mozilla::storage::BlobVariant(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 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 data(static_cast(buffer.get()), int(buffer.Length())); nsCOMPtr 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 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; } 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; } rv = aConnection->RemoveFunction(encoderName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection->SetSchemaVersion(MakeSchemaVersion(12, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult UpgradeSchemaFrom12_0To13_0(mozIStorageConnection* aConnection, bool* aVacuumNeeded) { AssertIsOnIOThread(); MOZ_ASSERT(aConnection); PROFILER_LABEL("IndexedDB", "UpgradeSchemaFrom12_0To13_0", js::ProfileEntry::Category::STORAGE); nsresult rv; #ifdef IDB_MOBILE int32_t defaultPageSize; rv = aConnection->GetDefaultPageSize(&defaultPageSize); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Enable auto_vacuum mode and update the page size to the platform default. nsAutoCString upgradeQuery("PRAGMA auto_vacuum = FULL; PRAGMA page_size = "); upgradeQuery.AppendInt(defaultPageSize); rv = aConnection->ExecuteSimpleSQL(upgradeQuery); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } *aVacuumNeeded = true; #endif rv = aConnection->SetSchemaVersion(MakeSchemaVersion(13, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult UpgradeSchemaFrom13_0To14_0(mozIStorageConnection* aConnection) { AssertIsOnIOThread(); MOZ_ASSERT(aConnection); // The only change between 13 and 14 was a different structured // clone format, but it's backwards-compatible. nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(14, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult UpgradeSchemaFrom14_0To15_0(mozIStorageConnection* aConnection) { // The only change between 14 and 15 was a different structured // clone format, but it's backwards-compatible. nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(15, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult UpgradeSchemaFrom15_0To16_0(mozIStorageConnection* aConnection) { // The only change between 15 and 16 was a different structured // clone format, but it's backwards-compatible. nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(16, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult UpgradeSchemaFrom16_0To17_0(mozIStorageConnection* aConnection) { // The only change between 16 and 17 was a different structured // clone format, but it's backwards-compatible. nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(17, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } class UpgradeSchemaFrom17_0To18_0Helper final { class InsertIndexDataValuesFunction; class UpgradeKeyFunction; public: static nsresult DoUpgrade(mozIStorageConnection* aConnection, const nsACString& aOrigin); private: static nsresult DoUpgradeInternal(mozIStorageConnection* aConnection, const nsACString& aOrigin); UpgradeSchemaFrom17_0To18_0Helper() { 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, fallible))) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_OUT_OF_MEMORY; } MOZ_ALWAYS_TRUE( indexValues.InsertElementSorted(IndexDataValue(indexId, !!unique, value), fallible)); // 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, const nsACString& aGroup, const nsACString& aOrigin, uint32_t aTelemetryId, nsIFileURL** aResult) { MOZ_ASSERT(aDatabaseFile); MOZ_ASSERT(aResult); nsCOMPtr uri; nsresult rv = NS_NewFileURI(getter_AddRefs(uri), aDatabaseFile); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr fileUrl = do_QueryInterface(uri); MOZ_ASSERT(fileUrl); nsAutoCString type; PersistenceTypeToText(aPersistenceType, type); nsAutoCString telemetryFilenameClause; if (aTelemetryId) { telemetryFilenameClause.AssignLiteral("&telemetryFilename=indexedDB-"); telemetryFilenameClause.AppendInt(aTelemetryId); telemetryFilenameClause.AppendLiteral(".sqlite"); } rv = fileUrl->SetQuery(NS_LITERAL_CSTRING("persistenceType=") + type + NS_LITERAL_CSTRING("&group=") + aGroup + NS_LITERAL_CSTRING("&origin=") + aOrigin + NS_LITERAL_CSTRING("&cache=private") + telemetryFilenameClause); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } fileUrl.forget(aResult); return NS_OK; } nsresult SetDefaultPragmas(mozIStorageConnection* aConnection) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(aConnection); 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 ";" // 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 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()) { 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) { // This is just an optimization so ignore the failure if the disk is // currently too full. rv = aConnection->SetGrowthIncrement(kSQLiteGrowthIncrement, EmptyCString()); if (rv != NS_ERROR_FILE_TOO_BIG && NS_WARN_IF(NS_FAILED(rv))) { return rv; } } #endif // IDB_MOBILE return NS_OK; } nsresult 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