зеркало из https://github.com/mozilla/gecko-dev.git
29630 строки
785 KiB
C++
29630 строки
785 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "ActorsParent.h"
|
|
|
|
#include <algorithm>
|
|
#include "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/AutoRestore.h"
|
|
#include "mozilla/Casting.h"
|
|
#include "mozilla/EndianUtils.h"
|
|
#include "mozilla/LazyIdleThread.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/SnappyCompressOutputStream.h"
|
|
#include "mozilla/SnappyUncompressInputStream.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "mozilla/storage.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/UniquePtrExtensions.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/dom/File.h"
|
|
#include "mozilla/dom/StructuredCloneTags.h"
|
|
#include "mozilla/dom/TabParent.h"
|
|
#include "mozilla/dom/filehandle/ActorsParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseRequestParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBTransactionParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsParent.h"
|
|
#include "mozilla/dom/indexedDB/PIndexedDBPermissionRequestParent.h"
|
|
#include "mozilla/dom/ipc/BlobParent.h"
|
|
#include "mozilla/dom/quota/Client.h"
|
|
#include "mozilla/dom/quota/FileStreams.h"
|
|
#include "mozilla/dom/quota/OriginScope.h"
|
|
#include "mozilla/dom/quota/QuotaManager.h"
|
|
#include "mozilla/dom/quota/UsageInfo.h"
|
|
#include "mozilla/ipc/BackgroundParent.h"
|
|
#include "mozilla/ipc/BackgroundUtils.h"
|
|
#include "mozilla/ipc/InputStreamParams.h"
|
|
#include "mozilla/ipc/InputStreamUtils.h"
|
|
#include "mozilla/ipc/PBackground.h"
|
|
#include "mozilla/ipc/PBackgroundParent.h"
|
|
#include "mozilla/Scoped.h"
|
|
#include "mozilla/storage/Variant.h"
|
|
#include "nsAutoPtr.h"
|
|
#include "nsCharSeparatedTokenizer.h"
|
|
#include "nsClassHashtable.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsDataHashtable.h"
|
|
#include "nsEscape.h"
|
|
#include "nsHashKeys.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsISimpleEnumerator.h"
|
|
#include "nsIEventTarget.h"
|
|
#include "nsIFile.h"
|
|
#include "nsIFileURL.h"
|
|
#include "nsIFileProtocolHandler.h"
|
|
#include "nsIInputStream.h"
|
|
#include "nsIInterfaceRequestor.h"
|
|
#include "nsInterfaceHashtable.h"
|
|
#include "nsIOutputStream.h"
|
|
#include "nsIPipe.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsISupports.h"
|
|
#include "nsISupportsImpl.h"
|
|
#include "nsISupportsPriority.h"
|
|
#include "nsIThread.h"
|
|
#include "nsITimer.h"
|
|
#include "nsIURI.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsQueryObject.h"
|
|
#include "nsRefPtrHashtable.h"
|
|
#include "nsStreamUtils.h"
|
|
#include "nsString.h"
|
|
#include "nsStringStream.h"
|
|
#include "nsThreadPool.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsXPCOMCID.h"
|
|
#include "PermissionRequestBase.h"
|
|
#include "ProfilerHelpers.h"
|
|
#include "prsystem.h"
|
|
#include "prtime.h"
|
|
#include "ReportInternalError.h"
|
|
#include "snappy/snappy.h"
|
|
|
|
#define DISABLE_ASSERTS_FOR_FUZZING 0
|
|
|
|
#if DISABLE_ASSERTS_FOR_FUZZING
|
|
#define ASSERT_UNLESS_FUZZING(...) do { } while (0)
|
|
#else
|
|
#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
|
|
#endif
|
|
|
|
#define IDB_DEBUG_LOG(_args) \
|
|
MOZ_LOG(IndexedDatabaseManager::GetLoggingModule(), \
|
|
LogLevel::Debug, \
|
|
_args )
|
|
|
|
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
|
|
#define IDB_MOBILE
|
|
#endif
|
|
|
|
#define BLOB_IMPL_STORED_FILE_IID \
|
|
{0x6b505c84, 0x2c60, 0x4ffb, {0x8b, 0x91, 0xfe, 0x22, 0xb1, 0xec, 0x75, 0xe2}}
|
|
|
|
namespace mozilla {
|
|
|
|
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc,
|
|
PRFileDesc,
|
|
PR_Close);
|
|
|
|
namespace dom {
|
|
namespace indexedDB {
|
|
|
|
using namespace mozilla::dom::quota;
|
|
using namespace mozilla::ipc;
|
|
|
|
namespace {
|
|
|
|
class ConnectionPool;
|
|
class Cursor;
|
|
class Database;
|
|
struct DatabaseActorInfo;
|
|
class DatabaseFile;
|
|
class DatabaseLoggingInfo;
|
|
class DatabaseMaintenance;
|
|
class Factory;
|
|
class Maintenance;
|
|
class MutableFile;
|
|
class OpenDatabaseOp;
|
|
class TransactionBase;
|
|
class TransactionDatabaseOperationBase;
|
|
class VersionChangeTransaction;
|
|
|
|
/*******************************************************************************
|
|
* Constants
|
|
******************************************************************************/
|
|
|
|
// If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
|
|
// schema version.
|
|
static_assert(JS_STRUCTURED_CLONE_VERSION == 8,
|
|
"Need to update the major schema version.");
|
|
|
|
// Major schema version. Bump for almost everything.
|
|
const uint32_t kMajorSchemaVersion = 25;
|
|
|
|
// Minor schema version. Should almost always be 0 (maybe bump on release
|
|
// branches if we have to).
|
|
const uint32_t kMinorSchemaVersion = 0;
|
|
|
|
// The schema version we store in the SQLite database is a (signed) 32-bit
|
|
// integer. The major version is left-shifted 4 bits so the max value is
|
|
// 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
|
|
static_assert(kMajorSchemaVersion <= 0xFFFFFFF,
|
|
"Major version needs to fit in 28 bits.");
|
|
static_assert(kMinorSchemaVersion <= 0xF,
|
|
"Minor version needs to fit in 4 bits.");
|
|
|
|
const int32_t kSQLiteSchemaVersion =
|
|
int32_t((kMajorSchemaVersion << 4) + kMinorSchemaVersion);
|
|
|
|
const int32_t kStorageProgressGranularity = 1000;
|
|
|
|
// Changing the value here will override the page size of new databases only.
|
|
// A journal mode change and VACUUM are needed to change existing databases, so
|
|
// the best way to do that is to use the schema version upgrade mechanism.
|
|
const uint32_t kSQLitePageSizeOverride =
|
|
#ifdef IDB_MOBILE
|
|
2048;
|
|
#else
|
|
4096;
|
|
#endif
|
|
|
|
static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 ||
|
|
(kSQLitePageSizeOverride % 2 == 0 &&
|
|
kSQLitePageSizeOverride >= 512 &&
|
|
kSQLitePageSizeOverride <= 65536),
|
|
"Must be 0 (disabled) or a power of 2 between 512 and 65536!");
|
|
|
|
// Set to -1 to use SQLite's default, 0 to disable, or some positive number to
|
|
// enforce a custom limit.
|
|
const int32_t kMaxWALPages = 5000; // 20MB on desktop, 10MB on mobile.
|
|
|
|
// Set to some multiple of the page size to grow the database in larger chunks.
|
|
const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2;
|
|
|
|
static_assert(kSQLiteGrowthIncrement >= 0 &&
|
|
kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
|
|
kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
|
|
"Must be 0 (disabled) or a positive multiple of the page size!");
|
|
|
|
// The maximum number of threads that can be used for database activity at a
|
|
// single time.
|
|
const uint32_t kMaxConnectionThreadCount = 20;
|
|
|
|
static_assert(kMaxConnectionThreadCount, "Must have at least one thread!");
|
|
|
|
// The maximum number of threads to keep when idle. Threads that become idle in
|
|
// excess of this number will be shut down immediately.
|
|
const uint32_t kMaxIdleConnectionThreadCount = 2;
|
|
|
|
static_assert(kMaxConnectionThreadCount >= kMaxIdleConnectionThreadCount,
|
|
"Idle thread limit must be less than total thread limit!");
|
|
|
|
// The length of time that database connections will be held open after all
|
|
// transactions have completed before doing idle maintenance.
|
|
const uint32_t kConnectionIdleMaintenanceMS = 2 * 1000; // 2 seconds
|
|
|
|
// The length of time that database connections will be held open after all
|
|
// transactions and maintenance have completed.
|
|
const uint32_t kConnectionIdleCloseMS = 10 * 1000; // 10 seconds
|
|
|
|
// The length of time that idle threads will stay alive before being shut down.
|
|
const uint32_t kConnectionThreadIdleMS = 30 * 1000; // 30 seconds
|
|
|
|
#define SAVEPOINT_CLAUSE "SAVEPOINT sp;"
|
|
|
|
const uint32_t kFileCopyBufferSize = 32768;
|
|
|
|
#define JOURNAL_DIRECTORY_NAME "journals"
|
|
|
|
const char kFileManagerDirectoryNameSuffix[] = ".files";
|
|
const char kSQLiteJournalSuffix[] = ".sqlite-journal";
|
|
const char kSQLiteSHMSuffix[] = ".sqlite-shm";
|
|
const char kSQLiteWALSuffix[] = ".sqlite-wal";
|
|
|
|
const char kPrefIndexedDBEnabled[] = "dom.indexedDB.enabled";
|
|
|
|
const char kPrefFileHandleEnabled[] = "dom.fileHandle.enabled";
|
|
|
|
#define IDB_PREFIX "indexedDB"
|
|
|
|
#define PERMISSION_STRING_CHROME_BASE IDB_PREFIX "-chrome-"
|
|
#define PERMISSION_STRING_CHROME_READ_SUFFIX "-read"
|
|
#define PERMISSION_STRING_CHROME_WRITE_SUFFIX "-write"
|
|
|
|
#ifdef DEBUG
|
|
|
|
const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
|
|
const uint32_t kDEBUGThreadSleepMS = 0;
|
|
|
|
const int32_t kDEBUGTransactionThreadPriority =
|
|
nsISupportsPriority::PRIORITY_NORMAL;
|
|
const uint32_t kDEBUGTransactionThreadSleepMS = 0;
|
|
|
|
#endif
|
|
|
|
template <size_t N>
|
|
constexpr size_t
|
|
LiteralStringLength(const char (&aArr)[N])
|
|
{
|
|
static_assert(N, "Zero-length string literal?!");
|
|
|
|
// Don't include the null terminator.
|
|
return N - 1;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Metadata classes
|
|
******************************************************************************/
|
|
|
|
struct FullIndexMetadata
|
|
{
|
|
IndexMetadata mCommonMetadata;
|
|
|
|
bool mDeleted;
|
|
|
|
public:
|
|
FullIndexMetadata()
|
|
: mCommonMetadata(0, nsString(), KeyPath(0), nsCString(), false, false, false)
|
|
, mDeleted(false)
|
|
{
|
|
// This can happen either on the QuotaManager IO thread or on a
|
|
// versionchange transaction thread. These threads can never race so this is
|
|
// totally safe.
|
|
}
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullIndexMetadata)
|
|
|
|
private:
|
|
~FullIndexMetadata() = default;
|
|
};
|
|
|
|
typedef nsRefPtrHashtable<nsUint64HashKey, FullIndexMetadata> IndexTable;
|
|
|
|
struct FullObjectStoreMetadata
|
|
{
|
|
ObjectStoreMetadata mCommonMetadata;
|
|
IndexTable mIndexes;
|
|
|
|
// These two members are only ever touched on a transaction thread!
|
|
int64_t mNextAutoIncrementId;
|
|
int64_t mCommittedAutoIncrementId;
|
|
|
|
bool mDeleted;
|
|
|
|
public:
|
|
FullObjectStoreMetadata()
|
|
: mCommonMetadata(0, nsString(), KeyPath(0), false)
|
|
, mNextAutoIncrementId(0)
|
|
, mCommittedAutoIncrementId(0)
|
|
, mDeleted(false)
|
|
{
|
|
// This can happen either on the QuotaManager IO thread or on a
|
|
// versionchange transaction thread. These threads can never race so this is
|
|
// totally safe.
|
|
}
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);
|
|
|
|
bool
|
|
HasLiveIndexes() const;
|
|
|
|
private:
|
|
~FullObjectStoreMetadata() = default;
|
|
};
|
|
|
|
typedef nsRefPtrHashtable<nsUint64HashKey, FullObjectStoreMetadata>
|
|
ObjectStoreTable;
|
|
|
|
struct FullDatabaseMetadata
|
|
{
|
|
DatabaseMetadata mCommonMetadata;
|
|
nsCString mDatabaseId;
|
|
nsString mFilePath;
|
|
ObjectStoreTable mObjectStores;
|
|
|
|
int64_t mNextObjectStoreId;
|
|
int64_t mNextIndexId;
|
|
|
|
public:
|
|
explicit FullDatabaseMetadata(const DatabaseMetadata& aCommonMetadata)
|
|
: mCommonMetadata(aCommonMetadata)
|
|
, mNextObjectStoreId(0)
|
|
, mNextIndexId(0)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
}
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullDatabaseMetadata)
|
|
|
|
already_AddRefed<FullDatabaseMetadata>
|
|
Duplicate() const;
|
|
|
|
private:
|
|
~FullDatabaseMetadata() = default;
|
|
};
|
|
|
|
template <class MetadataType>
|
|
class MOZ_STACK_CLASS MetadataNameOrIdMatcher final
|
|
{
|
|
typedef MetadataNameOrIdMatcher<MetadataType> SelfType;
|
|
|
|
const int64_t mId;
|
|
const nsString mName;
|
|
RefPtr<MetadataType> mMetadata;
|
|
bool mCheckName;
|
|
|
|
public:
|
|
template <class Enumerable>
|
|
static MetadataType*
|
|
Match(const Enumerable& aEnumerable, uint64_t aId, const nsAString& aName)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aId);
|
|
|
|
SelfType closure(aId, aName);
|
|
MatchHelper(aEnumerable, &closure);
|
|
|
|
return closure.mMetadata;
|
|
}
|
|
|
|
template <class Enumerable>
|
|
static MetadataType*
|
|
Match(const Enumerable& aEnumerable, uint64_t aId)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aId);
|
|
|
|
SelfType closure(aId);
|
|
MatchHelper(aEnumerable, &closure);
|
|
|
|
return closure.mMetadata;
|
|
}
|
|
|
|
private:
|
|
MetadataNameOrIdMatcher(const int64_t& aId, const nsAString& aName)
|
|
: mId(aId)
|
|
, mName(PromiseFlatString(aName))
|
|
, mMetadata(nullptr)
|
|
, mCheckName(true)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aId);
|
|
}
|
|
|
|
explicit MetadataNameOrIdMatcher(const int64_t& aId)
|
|
: mId(aId)
|
|
, mMetadata(nullptr)
|
|
, mCheckName(false)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aId);
|
|
}
|
|
|
|
template <class Enumerable>
|
|
static void
|
|
MatchHelper(const Enumerable& aEnumerable, SelfType* aClosure)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aClosure);
|
|
|
|
for (auto iter = aEnumerable.ConstIter(); !iter.Done(); iter.Next()) {
|
|
#ifdef DEBUG
|
|
const uint64_t key = iter.Key();
|
|
#endif
|
|
MetadataType* value = iter.UserData();
|
|
MOZ_ASSERT(key != 0);
|
|
MOZ_ASSERT(value);
|
|
|
|
if (!value->mDeleted &&
|
|
(aClosure->mId == value->mCommonMetadata.id() ||
|
|
(aClosure->mCheckName &&
|
|
aClosure->mName == value->mCommonMetadata.name()))) {
|
|
aClosure->mMetadata = value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
struct IndexDataValue final
|
|
{
|
|
int64_t mIndexId;
|
|
Key mKey;
|
|
Key mSortKey;
|
|
bool mUnique;
|
|
|
|
IndexDataValue()
|
|
: mIndexId(0)
|
|
, mUnique(false)
|
|
{
|
|
MOZ_COUNT_CTOR(IndexDataValue);
|
|
}
|
|
|
|
explicit
|
|
IndexDataValue(const IndexDataValue& aOther)
|
|
: mIndexId(aOther.mIndexId)
|
|
, mKey(aOther.mKey)
|
|
, mSortKey(aOther.mSortKey)
|
|
, mUnique(aOther.mUnique)
|
|
{
|
|
MOZ_ASSERT(!aOther.mKey.IsUnset());
|
|
|
|
MOZ_COUNT_CTOR(IndexDataValue);
|
|
}
|
|
|
|
IndexDataValue(int64_t aIndexId, bool aUnique, const Key& aKey)
|
|
: mIndexId(aIndexId)
|
|
, mKey(aKey)
|
|
, mUnique(aUnique)
|
|
{
|
|
MOZ_ASSERT(!aKey.IsUnset());
|
|
|
|
MOZ_COUNT_CTOR(IndexDataValue);
|
|
}
|
|
|
|
IndexDataValue(int64_t aIndexId, bool aUnique, const Key& aKey,
|
|
const Key& aSortKey)
|
|
: mIndexId(aIndexId)
|
|
, mKey(aKey)
|
|
, mSortKey(aSortKey)
|
|
, mUnique(aUnique)
|
|
{
|
|
MOZ_ASSERT(!aKey.IsUnset());
|
|
|
|
MOZ_COUNT_CTOR(IndexDataValue);
|
|
}
|
|
|
|
~IndexDataValue()
|
|
{
|
|
MOZ_COUNT_DTOR(IndexDataValue);
|
|
}
|
|
|
|
bool
|
|
operator==(const IndexDataValue& aOther) const
|
|
{
|
|
if (mIndexId != aOther.mIndexId) {
|
|
return false;
|
|
}
|
|
if (mSortKey.IsUnset()) {
|
|
return mKey == aOther.mKey;
|
|
}
|
|
return mSortKey == aOther.mSortKey;
|
|
}
|
|
|
|
bool
|
|
operator<(const IndexDataValue& aOther) const
|
|
{
|
|
if (mIndexId == aOther.mIndexId) {
|
|
if (mSortKey.IsUnset()) {
|
|
return mKey < aOther.mKey;
|
|
}
|
|
return mSortKey < aOther.mSortKey;
|
|
}
|
|
|
|
return mIndexId < aOther.mIndexId;
|
|
}
|
|
};
|
|
|
|
/*******************************************************************************
|
|
* SQLite functions
|
|
******************************************************************************/
|
|
|
|
int32_t
|
|
MakeSchemaVersion(uint32_t aMajorSchemaVersion,
|
|
uint32_t aMinorSchemaVersion)
|
|
{
|
|
return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion);
|
|
}
|
|
|
|
uint32_t
|
|
HashName(const nsAString& aName)
|
|
{
|
|
struct Helper
|
|
{
|
|
static uint32_t
|
|
RotateBitsLeft32(uint32_t aValue, uint8_t aBits)
|
|
{
|
|
MOZ_ASSERT(aBits < 32);
|
|
return (aValue << aBits) | (aValue >> (32 - aBits));
|
|
}
|
|
};
|
|
|
|
static const uint32_t kGoldenRatioU32 = 0x9e3779b9u;
|
|
|
|
const char16_t* str = aName.BeginReading();
|
|
size_t length = aName.Length();
|
|
|
|
uint32_t hash = 0;
|
|
for (size_t i = 0; i < length; i++) {
|
|
hash = kGoldenRatioU32 * (Helper::RotateBitsLeft32(hash, 5) ^ str[i]);
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
nsresult
|
|
ClampResultCode(nsresult aResultCode)
|
|
{
|
|
if (NS_SUCCEEDED(aResultCode) ||
|
|
NS_ERROR_GET_MODULE(aResultCode) == NS_ERROR_MODULE_DOM_INDEXEDDB) {
|
|
return aResultCode;
|
|
}
|
|
|
|
switch (aResultCode) {
|
|
case NS_ERROR_FILE_NO_DEVICE_SPACE:
|
|
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
|
|
case NS_ERROR_STORAGE_CONSTRAINT:
|
|
return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
|
|
default:
|
|
#ifdef DEBUG
|
|
nsPrintfCString message("Converting non-IndexedDB error code (0x%X) to "
|
|
"NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR",
|
|
aResultCode);
|
|
NS_WARNING(message.get());
|
|
#else
|
|
;
|
|
#endif
|
|
}
|
|
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
void
|
|
GetDatabaseFilename(const nsAString& aName,
|
|
nsAutoString& aDatabaseFilename)
|
|
{
|
|
MOZ_ASSERT(aDatabaseFilename.IsEmpty());
|
|
|
|
aDatabaseFilename.AppendInt(HashName(aName));
|
|
|
|
nsCString escapedName;
|
|
if (!NS_Escape(NS_ConvertUTF16toUTF8(aName), escapedName, url_XPAlphas)) {
|
|
MOZ_CRASH("Can't escape database name!");
|
|
}
|
|
|
|
const char* forwardIter = escapedName.BeginReading();
|
|
const char* backwardIter = escapedName.EndReading() - 1;
|
|
|
|
nsAutoCString substring;
|
|
while (forwardIter <= backwardIter && substring.Length() < 21) {
|
|
if (substring.Length() % 2) {
|
|
substring.Append(*backwardIter--);
|
|
} else {
|
|
substring.Append(*forwardIter++);
|
|
}
|
|
}
|
|
|
|
aDatabaseFilename.AppendASCII(substring.get(), substring.Length());
|
|
}
|
|
|
|
uint32_t
|
|
CompressedByteCountForNumber(uint64_t aNumber)
|
|
{
|
|
// All bytes have 7 bits available.
|
|
uint32_t count = 1;
|
|
while ((aNumber >>= 7)) {
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
uint32_t
|
|
CompressedByteCountForIndexId(int64_t aIndexId)
|
|
{
|
|
MOZ_ASSERT(aIndexId);
|
|
MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
|
|
"Overflow!");
|
|
|
|
return CompressedByteCountForNumber(uint64_t(aIndexId * 2));
|
|
}
|
|
|
|
void
|
|
WriteCompressedNumber(uint64_t aNumber, uint8_t** aIterator)
|
|
{
|
|
MOZ_ASSERT(aIterator);
|
|
MOZ_ASSERT(*aIterator);
|
|
|
|
uint8_t*& buffer = *aIterator;
|
|
|
|
#ifdef DEBUG
|
|
const uint8_t* bufferStart = buffer;
|
|
const uint64_t originalNumber = aNumber;
|
|
#endif
|
|
|
|
while (true) {
|
|
uint64_t shiftedNumber = aNumber >> 7;
|
|
if (shiftedNumber) {
|
|
*buffer++ = uint8_t(0x80 | (aNumber & 0x7f));
|
|
aNumber = shiftedNumber;
|
|
} else {
|
|
*buffer++ = uint8_t(aNumber);
|
|
break;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(buffer > bufferStart);
|
|
MOZ_ASSERT(uint32_t(buffer - bufferStart) ==
|
|
CompressedByteCountForNumber(originalNumber));
|
|
}
|
|
|
|
uint64_t
|
|
ReadCompressedNumber(const uint8_t** aIterator, const uint8_t* aEnd)
|
|
{
|
|
MOZ_ASSERT(aIterator);
|
|
MOZ_ASSERT(*aIterator);
|
|
MOZ_ASSERT(aEnd);
|
|
MOZ_ASSERT(*aIterator < aEnd);
|
|
|
|
const uint8_t*& buffer = *aIterator;
|
|
|
|
uint8_t shiftCounter = 0;
|
|
uint64_t result = 0;
|
|
|
|
while (true) {
|
|
MOZ_ASSERT(shiftCounter <= 56, "Shifted too many bits!");
|
|
|
|
result += (uint64_t(*buffer & 0x7f) << shiftCounter);
|
|
shiftCounter += 7;
|
|
|
|
if (!(*buffer++ & 0x80)) {
|
|
break;
|
|
}
|
|
|
|
if (NS_WARN_IF(buffer == aEnd)) {
|
|
MOZ_ASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
WriteCompressedIndexId(int64_t aIndexId, bool aUnique, uint8_t** aIterator)
|
|
{
|
|
MOZ_ASSERT(aIndexId);
|
|
MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
|
|
"Overflow!");
|
|
MOZ_ASSERT(aIterator);
|
|
MOZ_ASSERT(*aIterator);
|
|
|
|
const uint64_t indexId = (uint64_t(aIndexId * 2) | (aUnique ? 1 : 0));
|
|
WriteCompressedNumber(indexId, aIterator);
|
|
}
|
|
|
|
void
|
|
ReadCompressedIndexId(const uint8_t** aIterator,
|
|
const uint8_t* aEnd,
|
|
int64_t* aIndexId,
|
|
bool* aUnique)
|
|
{
|
|
MOZ_ASSERT(aIterator);
|
|
MOZ_ASSERT(*aIterator);
|
|
MOZ_ASSERT(aIndexId);
|
|
MOZ_ASSERT(aUnique);
|
|
|
|
uint64_t indexId = ReadCompressedNumber(aIterator, aEnd);
|
|
|
|
if (indexId % 2) {
|
|
*aUnique = true;
|
|
indexId--;
|
|
} else {
|
|
*aUnique = false;
|
|
}
|
|
|
|
MOZ_ASSERT(UINT64_MAX / 2 >= uint64_t(indexId), "Bad index id!");
|
|
|
|
*aIndexId = int64_t(indexId / 2);
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
MakeCompressedIndexDataValues(
|
|
const FallibleTArray<IndexDataValue>& aIndexValues,
|
|
UniqueFreePtr<uint8_t>& aCompressedIndexDataValues,
|
|
uint32_t* aCompressedIndexDataValuesLength)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(!aCompressedIndexDataValues);
|
|
MOZ_ASSERT(aCompressedIndexDataValuesLength);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"MakeCompressedIndexDataValues",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const uint32_t arrayLength = aIndexValues.Length();
|
|
if (!arrayLength) {
|
|
*aCompressedIndexDataValuesLength = 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
// First calculate the size of the final buffer.
|
|
uint32_t blobDataLength = 0;
|
|
|
|
for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
|
|
const IndexDataValue& info = aIndexValues[arrayIndex];
|
|
const nsCString& keyBuffer = info.mKey.GetBuffer();
|
|
const nsCString& sortKeyBuffer = info.mSortKey.GetBuffer();
|
|
const uint32_t keyBufferLength = keyBuffer.Length();
|
|
const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
|
|
|
|
MOZ_ASSERT(!keyBuffer.IsEmpty());
|
|
|
|
// Don't let |infoLength| overflow.
|
|
if (NS_WARN_IF(UINT32_MAX - keyBuffer.Length() <
|
|
CompressedByteCountForIndexId(info.mIndexId) +
|
|
CompressedByteCountForNumber(keyBufferLength) +
|
|
CompressedByteCountForNumber(sortKeyBufferLength))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
const uint32_t infoLength =
|
|
CompressedByteCountForIndexId(info.mIndexId) +
|
|
CompressedByteCountForNumber(keyBufferLength) +
|
|
CompressedByteCountForNumber(sortKeyBufferLength) +
|
|
keyBufferLength +
|
|
sortKeyBufferLength;
|
|
|
|
// 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<uint8_t> blobData(
|
|
static_cast<uint8_t*>(malloc(blobDataLength)));
|
|
if (NS_WARN_IF(!blobData)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
uint8_t* blobDataIter = blobData.get();
|
|
|
|
for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
|
|
const IndexDataValue& info = aIndexValues[arrayIndex];
|
|
const nsCString& keyBuffer = info.mKey.GetBuffer();
|
|
const nsCString& sortKeyBuffer = info.mSortKey.GetBuffer();
|
|
const uint32_t keyBufferLength = keyBuffer.Length();
|
|
const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
|
|
|
|
WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter);
|
|
WriteCompressedNumber(keyBufferLength, &blobDataIter);
|
|
|
|
memcpy(blobDataIter, keyBuffer.get(), keyBufferLength);
|
|
blobDataIter += keyBufferLength;
|
|
|
|
WriteCompressedNumber(sortKeyBufferLength, &blobDataIter);
|
|
|
|
memcpy(blobDataIter, sortKeyBuffer.get(), sortKeyBufferLength);
|
|
blobDataIter += sortKeyBufferLength;
|
|
}
|
|
|
|
MOZ_ASSERT(blobDataIter == blobData.get() + blobDataLength);
|
|
|
|
aCompressedIndexDataValues.swap(blobData);
|
|
*aCompressedIndexDataValuesLength = uint32_t(blobDataLength);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
ReadCompressedIndexDataValuesFromBlob(const uint8_t* aBlobData,
|
|
uint32_t aBlobDataLength,
|
|
nsTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aBlobData);
|
|
MOZ_ASSERT(aBlobDataLength);
|
|
MOZ_ASSERT(aIndexValues.IsEmpty());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ReadCompressedIndexDataValuesFromBlob",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
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<const char*>(blobDataIter),
|
|
uint32_t(keyBufferLength));
|
|
blobDataIter += keyBufferLength;
|
|
|
|
IndexDataValue idv(indexId, unique, Key(keyBuffer));
|
|
|
|
// Read sort key buffer length.
|
|
const uint64_t sortKeyBufferLength =
|
|
ReadCompressedNumber(&blobDataIter, blobDataEnd);
|
|
|
|
if (sortKeyBufferLength > 0) {
|
|
if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
|
|
NS_WARN_IF(sortKeyBufferLength > uint64_t(UINT32_MAX)) ||
|
|
NS_WARN_IF(blobDataIter + sortKeyBufferLength > blobDataEnd)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
nsCString sortKeyBuffer(reinterpret_cast<const char*>(blobDataIter),
|
|
uint32_t(sortKeyBufferLength));
|
|
blobDataIter += sortKeyBufferLength;
|
|
|
|
idv.mSortKey = Key(sortKeyBuffer);
|
|
}
|
|
|
|
if (NS_WARN_IF(!aIndexValues.InsertElementSorted(idv, fallible))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(blobDataIter == blobDataEnd);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
template <typename T>
|
|
nsresult
|
|
ReadCompressedIndexDataValuesFromSource(T* aSource,
|
|
uint32_t aColumnIndex,
|
|
nsTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aSource);
|
|
MOZ_ASSERT(aIndexValues.IsEmpty());
|
|
|
|
int32_t columnType;
|
|
nsresult rv = aSource->GetTypeOfIndex(aColumnIndex, &columnType);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) {
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_BLOB);
|
|
|
|
const uint8_t* blobData;
|
|
uint32_t blobDataLength;
|
|
rv = aSource->GetSharedBlob(aColumnIndex, &blobDataLength, &blobData);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!blobDataLength)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
rv = ReadCompressedIndexDataValuesFromBlob(blobData,
|
|
blobDataLength,
|
|
aIndexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
ReadCompressedIndexDataValues(mozIStorageStatement* aStatement,
|
|
uint32_t aColumnIndex,
|
|
nsTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
return ReadCompressedIndexDataValuesFromSource(aStatement,
|
|
aColumnIndex,
|
|
aIndexValues);
|
|
}
|
|
|
|
nsresult
|
|
ReadCompressedIndexDataValues(mozIStorageValueArray* aValues,
|
|
uint32_t aColumnIndex,
|
|
nsTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
return ReadCompressedIndexDataValuesFromSource(aValues,
|
|
aColumnIndex,
|
|
aIndexValues);
|
|
}
|
|
|
|
nsresult
|
|
CreateFileTables(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CreateFileTables",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// Table `file`
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE file ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"refcount INTEGER NOT NULL"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_insert_trigger "
|
|
"AFTER INSERT ON object_data "
|
|
"FOR EACH ROW "
|
|
"WHEN NEW.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(NULL, NEW.file_ids); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_update_trigger "
|
|
"AFTER UPDATE OF file_ids ON object_data "
|
|
"FOR EACH ROW "
|
|
"WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_delete_trigger "
|
|
"AFTER DELETE ON object_data "
|
|
"FOR EACH ROW WHEN OLD.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(OLD.file_ids, NULL); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER file_update_trigger "
|
|
"AFTER UPDATE ON file "
|
|
"FOR EACH ROW WHEN NEW.refcount = 0 "
|
|
"BEGIN "
|
|
"DELETE FROM file WHERE id = OLD.id; "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CreateTables(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CreateTables",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// Table `database`
|
|
|
|
// There are two reasons for having the origin column.
|
|
// First, we can ensure that we don't have collisions in the origin hash we
|
|
// use for the path because when we open the db we can make sure that the
|
|
// origins exactly match. Second, chrome code crawling through the idb
|
|
// directory can figure out the origin of every db without having to
|
|
// reverse-engineer our hash scheme.
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE database"
|
|
"( name TEXT PRIMARY KEY"
|
|
", origin TEXT NOT NULL"
|
|
", version INTEGER NOT NULL DEFAULT 0"
|
|
", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
|
|
", last_analyze_time INTEGER NOT NULL DEFAULT 0"
|
|
", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
|
|
") WITHOUT ROWID;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Table `object_store`
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_store"
|
|
"( id INTEGER PRIMARY KEY"
|
|
", auto_increment INTEGER NOT NULL DEFAULT 0"
|
|
", name TEXT NOT NULL"
|
|
", key_path TEXT"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Table `object_store_index`
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_store_index"
|
|
"( id INTEGER PRIMARY KEY"
|
|
", object_store_id INTEGER NOT NULL"
|
|
", name TEXT NOT NULL"
|
|
", key_path TEXT NOT NULL"
|
|
", unique_index INTEGER NOT NULL"
|
|
", multientry INTEGER NOT NULL"
|
|
", locale TEXT"
|
|
", is_auto_locale BOOLEAN NOT NULL"
|
|
", FOREIGN KEY (object_store_id) "
|
|
"REFERENCES object_store(id) "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Table `object_data`
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_data"
|
|
"( object_store_id INTEGER NOT NULL"
|
|
", key BLOB NOT NULL"
|
|
", index_data_values BLOB DEFAULT NULL"
|
|
", file_ids TEXT"
|
|
", data BLOB NOT NULL"
|
|
", PRIMARY KEY (object_store_id, key)"
|
|
", FOREIGN KEY (object_store_id) "
|
|
"REFERENCES object_store(id) "
|
|
") WITHOUT ROWID;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Table `index_data`
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE index_data"
|
|
"( index_id INTEGER NOT NULL"
|
|
", value BLOB NOT NULL"
|
|
", object_data_key BLOB NOT NULL"
|
|
", object_store_id INTEGER NOT NULL"
|
|
", value_locale BLOB"
|
|
", PRIMARY KEY (index_id, value, object_data_key)"
|
|
", FOREIGN KEY (index_id) "
|
|
"REFERENCES object_store_index(id) "
|
|
", FOREIGN KEY (object_store_id, object_data_key) "
|
|
"REFERENCES object_data(object_store_id, key) "
|
|
") WITHOUT ROWID;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX index_data_value_locale_index "
|
|
"ON index_data (index_id, value_locale, object_data_key, value) "
|
|
"WHERE value_locale IS NOT NULL;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Table `unique_index_data`
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE unique_index_data"
|
|
"( index_id INTEGER NOT NULL"
|
|
", value BLOB NOT NULL"
|
|
", object_store_id INTEGER NOT NULL"
|
|
", object_data_key BLOB NOT NULL"
|
|
", value_locale BLOB"
|
|
", PRIMARY KEY (index_id, value)"
|
|
", FOREIGN KEY (index_id) "
|
|
"REFERENCES object_store_index(id) "
|
|
", FOREIGN KEY (object_store_id, object_data_key) "
|
|
"REFERENCES object_data(object_store_id, key) "
|
|
") WITHOUT ROWID;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX unique_index_data_value_locale_index "
|
|
"ON unique_index_data (index_id, value_locale, object_data_key, value) "
|
|
"WHERE value_locale IS NOT NULL;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = CreateFileTables(aConnection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(kSQLiteSchemaVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom4To5(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom4To5",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv;
|
|
|
|
// All we changed is the type of the version column, so lets try to
|
|
// convert that to an integer, and if we fail, set it to 0.
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT name, version, dataVersion "
|
|
"FROM database"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsString name;
|
|
int32_t intVersion;
|
|
int64_t dataVersion;
|
|
|
|
{
|
|
mozStorageStatementScoper scoper(stmt);
|
|
|
|
bool hasResults;
|
|
rv = stmt->ExecuteStep(&hasResults);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
if (NS_WARN_IF(!hasResults)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsString version;
|
|
rv = stmt->GetString(1, version);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
intVersion = version.ToInteger(&rv);
|
|
if (NS_FAILED(rv)) {
|
|
intVersion = 0;
|
|
}
|
|
|
|
rv = stmt->GetString(0, name);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->GetInt64(2, &dataVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE database"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE database ("
|
|
"name TEXT NOT NULL, "
|
|
"version INTEGER NOT NULL DEFAULT 0, "
|
|
"dataVersion INTEGER NOT NULL"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"INSERT INTO database (name, version, dataVersion) "
|
|
"VALUES (:name, :version, :dataVersion)"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
{
|
|
mozStorageStatementScoper scoper(stmt);
|
|
|
|
rv = stmt->BindStringParameter(0, name);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt32Parameter(1, intVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64Parameter(2, dataVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(5);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom5To6(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom5To6",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// First, drop all the indexes we're no longer going to use.
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP INDEX key_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP INDEX ai_key_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP INDEX value_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP INDEX ai_value_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Now, reorder the columns of object_data to put the blob data last. We do
|
|
// this by copying into a temporary table, dropping the original, then copying
|
|
// back into a newly created table.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"object_store_id, "
|
|
"key_value, "
|
|
"data "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT id, object_store_id, key_value, data "
|
|
"FROM object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_data ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"object_store_id INTEGER NOT NULL, "
|
|
"key_value DEFAULT NULL, "
|
|
"data BLOB NOT NULL, "
|
|
"UNIQUE (object_store_id, key_value), "
|
|
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_data "
|
|
"SELECT id, object_store_id, key_value, data "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// We need to add a unique constraint to our ai_object_data table. Copy all
|
|
// the data out of it using a temporary table as before.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"object_store_id, "
|
|
"data "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT id, object_store_id, data "
|
|
"FROM ai_object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE ai_object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE ai_object_data ("
|
|
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
|
"object_store_id INTEGER NOT NULL, "
|
|
"data BLOB NOT NULL, "
|
|
"UNIQUE (object_store_id, id), "
|
|
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO ai_object_data "
|
|
"SELECT id, object_store_id, data "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Fix up the index_data table. We're reordering the columns as well as
|
|
// changing the primary key from being a simple id to being a composite.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"index_id, "
|
|
"value, "
|
|
"object_data_key, "
|
|
"object_data_id "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT index_id, value, object_data_key, object_data_id "
|
|
"FROM index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE index_data ("
|
|
"index_id INTEGER NOT NULL, "
|
|
"value NOT NULL, "
|
|
"object_data_key NOT NULL, "
|
|
"object_data_id INTEGER NOT NULL, "
|
|
"PRIMARY KEY (index_id, value, object_data_key), "
|
|
"FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
|
|
"CASCADE, "
|
|
"FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT OR IGNORE INTO index_data "
|
|
"SELECT index_id, value, object_data_key, object_data_id "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX index_data_object_data_id_index "
|
|
"ON index_data (object_data_id);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Fix up the unique_index_data table. We're reordering the columns as well as
|
|
// changing the primary key from being a simple id to being a composite.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"index_id, "
|
|
"value, "
|
|
"object_data_key, "
|
|
"object_data_id "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT index_id, value, object_data_key, object_data_id "
|
|
"FROM unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE unique_index_data ("
|
|
"index_id INTEGER NOT NULL, "
|
|
"value NOT NULL, "
|
|
"object_data_key NOT NULL, "
|
|
"object_data_id INTEGER NOT NULL, "
|
|
"PRIMARY KEY (index_id, value, object_data_key), "
|
|
"UNIQUE (index_id, value), "
|
|
"FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
|
|
"CASCADE "
|
|
"FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO unique_index_data "
|
|
"SELECT index_id, value, object_data_key, object_data_id "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX unique_index_data_object_data_id_index "
|
|
"ON unique_index_data (object_data_id);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Fix up the ai_index_data table. We're reordering the columns as well as
|
|
// changing the primary key from being a simple id to being a composite.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"index_id, "
|
|
"value, "
|
|
"ai_object_data_id "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT index_id, value, ai_object_data_id "
|
|
"FROM ai_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE ai_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE ai_index_data ("
|
|
"index_id INTEGER NOT NULL, "
|
|
"value NOT NULL, "
|
|
"ai_object_data_id INTEGER NOT NULL, "
|
|
"PRIMARY KEY (index_id, value, ai_object_data_id), "
|
|
"FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
|
|
"CASCADE, "
|
|
"FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT OR IGNORE INTO ai_index_data "
|
|
"SELECT index_id, value, ai_object_data_id "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX ai_index_data_ai_object_data_id_index "
|
|
"ON ai_index_data (ai_object_data_id);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Fix up the ai_unique_index_data table. We're reordering the columns as well
|
|
// as changing the primary key from being a simple id to being a composite.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"index_id, "
|
|
"value, "
|
|
"ai_object_data_id "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT index_id, value, ai_object_data_id "
|
|
"FROM ai_unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE ai_unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE ai_unique_index_data ("
|
|
"index_id INTEGER NOT NULL, "
|
|
"value NOT NULL, "
|
|
"ai_object_data_id INTEGER NOT NULL, "
|
|
"UNIQUE (index_id, value), "
|
|
"PRIMARY KEY (index_id, value, ai_object_data_id), "
|
|
"FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
|
|
"CASCADE, "
|
|
"FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO ai_unique_index_data "
|
|
"SELECT index_id, value, ai_object_data_id "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX ai_unique_index_data_ai_object_data_id_index "
|
|
"ON ai_unique_index_data (ai_object_data_id);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(6);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom6To7(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom6To7",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"id, "
|
|
"name, "
|
|
"key_path, "
|
|
"auto_increment"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT id, name, key_path, auto_increment "
|
|
"FROM object_store;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE object_store;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_store ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"auto_increment INTEGER NOT NULL DEFAULT 0, "
|
|
"name TEXT NOT NULL, "
|
|
"key_path TEXT, "
|
|
"UNIQUE (name)"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_store "
|
|
"SELECT id, auto_increment, name, nullif(key_path, '') "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(7);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom7To8(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom7To8",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"id, "
|
|
"object_store_id, "
|
|
"name, "
|
|
"key_path, "
|
|
"unique_index, "
|
|
"object_store_autoincrement"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT id, object_store_id, name, key_path, "
|
|
"unique_index, object_store_autoincrement "
|
|
"FROM object_store_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE object_store_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_store_index ("
|
|
"id INTEGER, "
|
|
"object_store_id INTEGER NOT NULL, "
|
|
"name TEXT NOT NULL, "
|
|
"key_path TEXT NOT NULL, "
|
|
"unique_index INTEGER NOT NULL, "
|
|
"multientry INTEGER NOT NULL, "
|
|
"object_store_autoincrement INTERGER NOT NULL, "
|
|
"PRIMARY KEY (id), "
|
|
"UNIQUE (object_store_id, name), "
|
|
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_store_index "
|
|
"SELECT id, object_store_id, name, key_path, "
|
|
"unique_index, 0, object_store_autoincrement "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(8);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
class CompressDataBlobsFunction final
|
|
: public mozIStorageFunction
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
private:
|
|
~CompressDataBlobsFunction() = default;
|
|
|
|
NS_IMETHOD
|
|
OnFunctionCall(mozIStorageValueArray* aArguments,
|
|
nsIVariant** aResult) override
|
|
{
|
|
MOZ_ASSERT(aArguments);
|
|
MOZ_ASSERT(aResult);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CompressDataBlobsFunction::OnFunctionCall",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
uint32_t argc;
|
|
nsresult rv = aArguments->GetNumEntries(&argc);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (argc != 1) {
|
|
NS_WARNING("Don't call me with the wrong number of arguments!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
int32_t type;
|
|
rv = aArguments->GetTypeOfIndex(0, &type);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (type != mozIStorageStatement::VALUE_TYPE_BLOB) {
|
|
NS_WARNING("Don't call me with the wrong type of arguments!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
const uint8_t* uncompressed;
|
|
uint32_t uncompressedLength;
|
|
rv = aArguments->GetSharedBlob(0, &uncompressedLength, &uncompressed);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
|
|
UniqueFreePtr<uint8_t> compressed(
|
|
static_cast<uint8_t*>(malloc(compressedLength)));
|
|
if (NS_WARN_IF(!compressed)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
snappy::RawCompress(reinterpret_cast<const char*>(uncompressed),
|
|
uncompressedLength,
|
|
reinterpret_cast<char*>(compressed.get()),
|
|
&compressedLength);
|
|
|
|
std::pair<uint8_t *, int> data(compressed.release(),
|
|
int(compressedLength));
|
|
|
|
nsCOMPtr<nsIVariant> result = new mozilla::storage::AdoptedBlobVariant(data);
|
|
|
|
result.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom8To9_0(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom8To9_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// We no longer use the dataVersion column.
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"UPDATE database SET dataVersion = 0;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageFunction> compressor = new CompressDataBlobsFunction();
|
|
|
|
NS_NAMED_LITERAL_CSTRING(compressorName, "compress");
|
|
|
|
rv = aConnection->CreateFunction(compressorName, 1, compressor);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Turn off foreign key constraints before we do anything here.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"UPDATE object_data SET data = compress(data);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"UPDATE ai_object_data SET data = compress(data);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->RemoveFunction(compressorName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(9, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom9_0To10_0(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom9_0To10_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"ALTER TABLE object_data ADD COLUMN file_ids TEXT;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"ALTER TABLE ai_object_data ADD COLUMN file_ids TEXT;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = CreateFileTables(aConnection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(10, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom10_0To11_0(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom10_0To11_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"id, "
|
|
"object_store_id, "
|
|
"name, "
|
|
"key_path, "
|
|
"unique_index, "
|
|
"multientry"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT id, object_store_id, name, key_path, "
|
|
"unique_index, multientry "
|
|
"FROM object_store_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE object_store_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_store_index ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"object_store_id INTEGER NOT NULL, "
|
|
"name TEXT NOT NULL, "
|
|
"key_path TEXT NOT NULL, "
|
|
"unique_index INTEGER NOT NULL, "
|
|
"multientry INTEGER NOT NULL, "
|
|
"UNIQUE (object_store_id, name), "
|
|
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_store_index "
|
|
"SELECT id, object_store_id, name, key_path, "
|
|
"unique_index, multientry "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TRIGGER object_data_insert_trigger;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_data (object_store_id, key_value, data, file_ids) "
|
|
"SELECT object_store_id, id, data, file_ids "
|
|
"FROM ai_object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_insert_trigger "
|
|
"AFTER INSERT ON object_data "
|
|
"FOR EACH ROW "
|
|
"WHEN NEW.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(NULL, NEW.file_ids); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO index_data (index_id, value, object_data_key, object_data_id) "
|
|
"SELECT ai_index_data.index_id, ai_index_data.value, ai_index_data.ai_object_data_id, object_data.id "
|
|
"FROM ai_index_data "
|
|
"INNER JOIN object_store_index ON "
|
|
"object_store_index.id = ai_index_data.index_id "
|
|
"INNER JOIN object_data ON "
|
|
"object_data.object_store_id = object_store_index.object_store_id AND "
|
|
"object_data.key_value = ai_index_data.ai_object_data_id;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO unique_index_data (index_id, value, object_data_key, object_data_id) "
|
|
"SELECT ai_unique_index_data.index_id, ai_unique_index_data.value, ai_unique_index_data.ai_object_data_id, object_data.id "
|
|
"FROM ai_unique_index_data "
|
|
"INNER JOIN object_store_index ON "
|
|
"object_store_index.id = ai_unique_index_data.index_id "
|
|
"INNER JOIN object_data ON "
|
|
"object_data.object_store_id = object_store_index.object_store_id AND "
|
|
"object_data.key_value = ai_unique_index_data.ai_object_data_id;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"UPDATE object_store "
|
|
"SET auto_increment = (SELECT max(id) FROM ai_object_data) + 1 "
|
|
"WHERE auto_increment;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE ai_unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE ai_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE ai_object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(11, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
class EncodeKeysFunction final
|
|
: public mozIStorageFunction
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
private:
|
|
~EncodeKeysFunction() = default;
|
|
|
|
NS_IMETHOD
|
|
OnFunctionCall(mozIStorageValueArray* aArguments,
|
|
nsIVariant** aResult) override
|
|
{
|
|
MOZ_ASSERT(aArguments);
|
|
MOZ_ASSERT(aResult);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"EncodeKeysFunction::OnFunctionCall",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
uint32_t argc;
|
|
nsresult rv = aArguments->GetNumEntries(&argc);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (argc != 1) {
|
|
NS_WARNING("Don't call me with the wrong number of arguments!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
int32_t type;
|
|
rv = aArguments->GetTypeOfIndex(0, &type);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
Key key;
|
|
if (type == mozIStorageStatement::VALUE_TYPE_INTEGER) {
|
|
int64_t intKey;
|
|
aArguments->GetInt64(0, &intKey);
|
|
key.SetFromInteger(intKey);
|
|
} else if (type == mozIStorageStatement::VALUE_TYPE_TEXT) {
|
|
nsString stringKey;
|
|
aArguments->GetString(0, stringKey);
|
|
key.SetFromString(stringKey);
|
|
} else {
|
|
NS_WARNING("Don't call me with the wrong type of arguments!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
const nsCString& buffer = key.GetBuffer();
|
|
|
|
std::pair<const void *, int> data(static_cast<const void*>(buffer.get()),
|
|
int(buffer.Length()));
|
|
|
|
nsCOMPtr<nsIVariant> result = new mozilla::storage::BlobVariant(data);
|
|
|
|
result.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom11_0To12_0(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom11_0To12_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
NS_NAMED_LITERAL_CSTRING(encoderName, "encode");
|
|
|
|
nsCOMPtr<mozIStorageFunction> encoder = new EncodeKeysFunction();
|
|
|
|
nsresult rv = aConnection->CreateFunction(encoderName, 1, encoder);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"object_store_id, "
|
|
"key_value, "
|
|
"data, "
|
|
"file_ids "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT id, object_store_id, encode(key_value), data, file_ids "
|
|
"FROM object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_data ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"object_store_id INTEGER NOT NULL, "
|
|
"key_value BLOB DEFAULT NULL, "
|
|
"file_ids TEXT, "
|
|
"data BLOB NOT NULL, "
|
|
"UNIQUE (object_store_id, key_value), "
|
|
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_data "
|
|
"SELECT id, object_store_id, key_value, file_ids, data "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_insert_trigger "
|
|
"AFTER INSERT ON object_data "
|
|
"FOR EACH ROW "
|
|
"WHEN NEW.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(NULL, NEW.file_ids); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_update_trigger "
|
|
"AFTER UPDATE OF file_ids ON object_data "
|
|
"FOR EACH ROW "
|
|
"WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_delete_trigger "
|
|
"AFTER DELETE ON object_data "
|
|
"FOR EACH ROW WHEN OLD.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(OLD.file_ids, NULL); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"index_id, "
|
|
"value, "
|
|
"object_data_key, "
|
|
"object_data_id "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT index_id, encode(value), encode(object_data_key), object_data_id "
|
|
"FROM index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE index_data ("
|
|
"index_id INTEGER NOT NULL, "
|
|
"value BLOB NOT NULL, "
|
|
"object_data_key BLOB NOT NULL, "
|
|
"object_data_id INTEGER NOT NULL, "
|
|
"PRIMARY KEY (index_id, value, object_data_key), "
|
|
"FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
|
|
"CASCADE, "
|
|
"FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO index_data "
|
|
"SELECT index_id, value, object_data_key, object_data_id "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX index_data_object_data_id_index "
|
|
"ON index_data (object_data_id);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"index_id, "
|
|
"value, "
|
|
"object_data_key, "
|
|
"object_data_id "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT index_id, encode(value), encode(object_data_key), object_data_id "
|
|
"FROM unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE unique_index_data ("
|
|
"index_id INTEGER NOT NULL, "
|
|
"value BLOB NOT NULL, "
|
|
"object_data_key BLOB NOT NULL, "
|
|
"object_data_id INTEGER NOT NULL, "
|
|
"PRIMARY KEY (index_id, value, object_data_key), "
|
|
"UNIQUE (index_id, value), "
|
|
"FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
|
|
"CASCADE "
|
|
"FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO unique_index_data "
|
|
"SELECT index_id, value, object_data_key, object_data_id "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
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() = default;
|
|
|
|
NS_DECL_MOZISTORAGEFUNCTION
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper::
|
|
InsertIndexDataValuesFunction,
|
|
mozIStorageFunction);
|
|
|
|
NS_IMETHODIMP
|
|
UpgradeSchemaFrom17_0To18_0Helper::
|
|
InsertIndexDataValuesFunction::OnFunctionCall(mozIStorageValueArray* aValues,
|
|
nsIVariant** _retval)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aValues);
|
|
MOZ_ASSERT(_retval);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
uint32_t argCount;
|
|
MOZ_ALWAYS_SUCCEEDS(aValues->GetNumEntries(&argCount));
|
|
MOZ_ASSERT(argCount == 4);
|
|
|
|
int32_t valueType;
|
|
MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType));
|
|
MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
|
|
valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(1, &valueType));
|
|
MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(2, &valueType));
|
|
MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(3, &valueType));
|
|
MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
|
|
}
|
|
#endif
|
|
|
|
// Read out the previous value. It may be NULL, in which case we'll just end
|
|
// up with an empty array.
|
|
AutoTArray<IndexDataValue, 32> 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<uint8_t> 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<uint8_t *, int> indexValuesBlobPair(indexValuesBlob.release(),
|
|
indexValuesBlobLength);
|
|
|
|
nsCOMPtr<nsIVariant> 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() = default;
|
|
|
|
static nsresult
|
|
CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource,
|
|
const uint8_t* aSourceEnd,
|
|
uint8_t*& aDestination,
|
|
uint8_t aTagOffset,
|
|
uint8_t aRecursionDepth);
|
|
|
|
static uint32_t
|
|
AdjustedSize(uint32_t aMaxSize,
|
|
const uint8_t* aSource,
|
|
const uint8_t* aSourceEnd)
|
|
{
|
|
MOZ_ASSERT(aMaxSize);
|
|
MOZ_ASSERT(aSource);
|
|
MOZ_ASSERT(aSourceEnd);
|
|
MOZ_ASSERT(aSource <= aSourceEnd);
|
|
|
|
return std::min(aMaxSize, uint32_t(aSourceEnd - aSource));
|
|
}
|
|
|
|
NS_DECL_MOZISTORAGEFUNCTION
|
|
};
|
|
|
|
// static
|
|
nsresult
|
|
UpgradeSchemaFrom17_0To18_0Helper::
|
|
UpgradeKeyFunction::CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource,
|
|
const uint8_t* aSourceEnd,
|
|
uint8_t*& aDestination,
|
|
uint8_t aTagOffset,
|
|
uint8_t aRecursionDepth)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aSource);
|
|
MOZ_ASSERT(*aSource);
|
|
MOZ_ASSERT(aSourceEnd);
|
|
MOZ_ASSERT(aSource < aSourceEnd);
|
|
MOZ_ASSERT(aDestination);
|
|
MOZ_ASSERT(aTagOffset <= Key::kMaxArrayCollapse);
|
|
|
|
static constexpr uint8_t kOldNumberTag = 0x1;
|
|
static constexpr uint8_t kOldDateTag = 0x2;
|
|
static constexpr uint8_t kOldStringTag = 0x3;
|
|
static constexpr uint8_t kOldArrayTag = 0x4;
|
|
static constexpr uint8_t kOldMaxType = kOldArrayTag;
|
|
|
|
if (NS_WARN_IF(aRecursionDepth > Key::kMaxRecursionDepth)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
const uint8_t sourceTag = *aSource - (aTagOffset * kOldMaxType);
|
|
MOZ_ASSERT(sourceTag);
|
|
|
|
if (NS_WARN_IF(sourceTag > kOldMaxType * Key::kMaxArrayCollapse)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
if (sourceTag == kOldNumberTag || sourceTag == kOldDateTag) {
|
|
// Write the new tag.
|
|
*aDestination++ =
|
|
(sourceTag == kOldNumberTag ? Key::eFloat : Key::eDate) +
|
|
(aTagOffset * Key::eMaxType);
|
|
aSource++;
|
|
|
|
// Numbers and Dates are encoded as 64-bit integers, but trailing 0
|
|
// bytes have been removed.
|
|
const uint32_t byteCount =
|
|
AdjustedSize(sizeof(uint64_t), aSource, aSourceEnd);
|
|
|
|
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_SUCCEEDS(aValues->GetNumEntries(&argCount));
|
|
MOZ_ASSERT(argCount == 1);
|
|
|
|
int32_t valueType;
|
|
MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType));
|
|
MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
|
|
}
|
|
#endif
|
|
|
|
// Dig the old key out of the values.
|
|
const uint8_t* blobData;
|
|
uint32_t blobDataLength;
|
|
nsresult rv = aValues->GetSharedBlob(0, &blobDataLength, &blobData);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Upgrading the key doesn't change the amount of space needed to hold it.
|
|
UniqueFreePtr<uint8_t> upgradedBlobData(
|
|
static_cast<uint8_t*>(malloc(blobDataLength)));
|
|
if (NS_WARN_IF(!upgradedBlobData)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
rv = CopyAndUpgradeKeyBuffer(blobData,
|
|
blobData + blobDataLength,
|
|
upgradedBlobData.get());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// The upgraded key is the result of this function.
|
|
std::pair<uint8_t*, int> data(upgradedBlobData.release(),
|
|
int(blobDataLength));
|
|
|
|
nsCOMPtr<nsIVariant> result = new mozilla::storage::AdoptedBlobVariant(data);
|
|
|
|
result.forget(_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(mozIStorageConnection* aConnection,
|
|
const nsACString& aOrigin)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
MOZ_ASSERT(!aOrigin.IsEmpty());
|
|
|
|
// Register the |upgrade_key| function.
|
|
RefPtr<UpgradeKeyFunction> 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.
|
|
RefPtr<InsertIndexDataValuesFunction> 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_SUCCEEDS(aConnection->RemoveFunction(upgradeKeyFunctionName));
|
|
return rv;
|
|
}
|
|
|
|
rv = DoUpgradeInternal(aConnection, aOrigin);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(aConnection->RemoveFunction(upgradeKeyFunctionName));
|
|
MOZ_ALWAYS_SUCCEEDS(aConnection->RemoveFunction(insertIDVFunctionName));
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
UpgradeSchemaFrom17_0To18_0Helper::DoUpgradeInternal(
|
|
mozIStorageConnection* aConnection,
|
|
const nsACString& aOrigin)
|
|
{
|
|
MOZ_ASSERT(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<mozIStorageStatement> 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<mozIStorageStatement> 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
|
|
UpgradeSchemaFrom18_0To19_0(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
nsresult rv;
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom18_0To19_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"ALTER TABLE object_store_index "
|
|
"ADD COLUMN locale TEXT;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"ALTER TABLE object_store_index "
|
|
"ADD COLUMN is_auto_locale BOOLEAN;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"ALTER TABLE index_data "
|
|
"ADD COLUMN value_locale BLOB;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"ALTER TABLE unique_index_data "
|
|
"ADD COLUMN value_locale BLOB;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX index_data_value_locale_index "
|
|
"ON index_data (index_id, value_locale, object_data_key, value) "
|
|
"WHERE value_locale IS NOT NULL;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX unique_index_data_value_locale_index "
|
|
"ON unique_index_data (index_id, value_locale, object_data_key, value) "
|
|
"WHERE value_locale IS NOT NULL;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(19, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
#if !defined(MOZ_B2G)
|
|
|
|
class NormalJSContext;
|
|
|
|
class UpgradeFileIdsFunction final
|
|
: public mozIStorageFunction
|
|
{
|
|
RefPtr<FileManager> mFileManager;
|
|
nsAutoPtr<NormalJSContext> mContext;
|
|
|
|
public:
|
|
UpgradeFileIdsFunction()
|
|
{
|
|
AssertIsOnIOThread();
|
|
}
|
|
|
|
nsresult
|
|
Init(nsIFile* aFMDirectory,
|
|
mozIStorageConnection* aConnection);
|
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
private:
|
|
~UpgradeFileIdsFunction()
|
|
{
|
|
AssertIsOnIOThread();
|
|
|
|
if (mFileManager) {
|
|
mFileManager->Invalidate();
|
|
}
|
|
}
|
|
|
|
NS_IMETHOD
|
|
OnFunctionCall(mozIStorageValueArray* aArguments,
|
|
nsIVariant** aResult) override;
|
|
};
|
|
|
|
#endif // MOZ_B2G
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom19_0To20_0(nsIFile* aFMDirectory,
|
|
mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom19_0To20_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
#if defined(MOZ_B2G)
|
|
|
|
// We don't have to do the upgrade of file ids on B2G. The old format was
|
|
// only used by the previous single process implementation and B2G was
|
|
// always multi process. This is a nice optimization since the upgrade needs
|
|
// to deserialize all structured clones which reference a stored file or
|
|
// a mutable file.
|
|
nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
#else // MOZ_B2G
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT count(*) "
|
|
"FROM object_data "
|
|
"WHERE file_ids IS NOT NULL"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
int64_t count;
|
|
|
|
{
|
|
mozStorageStatementScoper scoper(stmt);
|
|
|
|
bool hasResult;
|
|
rv = stmt->ExecuteStep(&hasResult);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!hasResult)) {
|
|
MOZ_ASSERT(false, "This should never be possible!");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
count = stmt->AsInt64(0);
|
|
if (NS_WARN_IF(count < 0)) {
|
|
MOZ_ASSERT(false, "This should never be possible!");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (count == 0) {
|
|
// Nothing to upgrade.
|
|
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<UpgradeFileIdsFunction> function = new UpgradeFileIdsFunction();
|
|
|
|
rv = function->Init(aFMDirectory, aConnection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
NS_NAMED_LITERAL_CSTRING(functionName, "upgrade");
|
|
|
|
rv = aConnection->CreateFunction(functionName, 2, function);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Disable update trigger.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TRIGGER object_data_update_trigger;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"UPDATE object_data "
|
|
"SET file_ids = upgrade(file_ids, data) "
|
|
"WHERE file_ids IS NOT NULL;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Enable update trigger.
|
|
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->RemoveFunction(functionName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
#endif // MOZ_B2G
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
class UpgradeIndexDataValuesFunction final
|
|
: public mozIStorageFunction
|
|
{
|
|
public:
|
|
UpgradeIndexDataValuesFunction()
|
|
{
|
|
AssertIsOnIOThread();
|
|
}
|
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
private:
|
|
~UpgradeIndexDataValuesFunction()
|
|
{
|
|
AssertIsOnIOThread();
|
|
}
|
|
|
|
nsresult
|
|
ReadOldCompressedIDVFromBlob(const uint8_t* aBlobData,
|
|
uint32_t aBlobDataLength,
|
|
nsTArray<IndexDataValue>& aIndexValues);
|
|
|
|
NS_IMETHOD
|
|
OnFunctionCall(mozIStorageValueArray* aArguments,
|
|
nsIVariant** aResult) override;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(UpgradeIndexDataValuesFunction, mozIStorageFunction)
|
|
|
|
nsresult
|
|
UpgradeIndexDataValuesFunction::ReadOldCompressedIDVFromBlob(
|
|
const uint8_t* aBlobData,
|
|
uint32_t aBlobDataLength,
|
|
nsTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aBlobData);
|
|
MOZ_ASSERT(aBlobDataLength);
|
|
MOZ_ASSERT(aIndexValues.IsEmpty());
|
|
|
|
const uint8_t* blobDataIter = aBlobData;
|
|
const uint8_t* blobDataEnd = aBlobData + aBlobDataLength;
|
|
|
|
int64_t indexId;
|
|
bool unique;
|
|
bool nextIndexIdAlreadyRead = false;
|
|
|
|
while (blobDataIter < blobDataEnd) {
|
|
if (!nextIndexIdAlreadyRead) {
|
|
ReadCompressedIndexId(&blobDataIter, blobDataEnd, &indexId, &unique);
|
|
}
|
|
nextIndexIdAlreadyRead = false;
|
|
|
|
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<const char*>(blobDataIter),
|
|
uint32_t(keyBufferLength));
|
|
blobDataIter += keyBufferLength;
|
|
|
|
IndexDataValue idv(indexId, unique, Key(keyBuffer));
|
|
|
|
if (blobDataIter < blobDataEnd) {
|
|
// Read either a sort key buffer length or an index id.
|
|
uint64_t maybeIndexId = ReadCompressedNumber(&blobDataIter, blobDataEnd);
|
|
|
|
// Locale-aware indexes haven't been around long enough to have any users,
|
|
// we can safely assume all sort key buffer lengths will be zero.
|
|
if (maybeIndexId != 0) {
|
|
if (maybeIndexId % 2) {
|
|
unique = true;
|
|
maybeIndexId--;
|
|
} else {
|
|
unique = false;
|
|
}
|
|
indexId = maybeIndexId/2;
|
|
nextIndexIdAlreadyRead = true;
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(!aIndexValues.InsertElementSorted(idv, fallible))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(blobDataIter == blobDataEnd);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
UpgradeIndexDataValuesFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
|
|
nsIVariant** aResult)
|
|
{
|
|
MOZ_ASSERT(aArguments);
|
|
MOZ_ASSERT(aResult);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeIndexDataValuesFunction::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* oldBlob;
|
|
uint32_t oldBlobLength;
|
|
rv = aArguments->GetSharedBlob(0, &oldBlobLength, &oldBlob);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
AutoTArray<IndexDataValue, 32> oldIdv;
|
|
rv = ReadOldCompressedIDVFromBlob(oldBlob, oldBlobLength, oldIdv);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
UniqueFreePtr<uint8_t> newIdv;
|
|
uint32_t newIdvLength;
|
|
rv = MakeCompressedIndexDataValues(oldIdv, newIdv, &newIdvLength);
|
|
|
|
std::pair<uint8_t*, int> data(newIdv.release(), newIdvLength);
|
|
|
|
nsCOMPtr<nsIVariant> result = new storage::AdoptedBlobVariant(data);
|
|
|
|
result.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom20_0To21_0(mozIStorageConnection* aConnection)
|
|
{
|
|
// This should have been part of the 18 to 19 upgrade, where we changed the
|
|
// layout of the index_data_values blobs but didn't upgrade the existing data.
|
|
// See bug 1202788.
|
|
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom20_0To21_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
RefPtr<UpgradeIndexDataValuesFunction> function =
|
|
new UpgradeIndexDataValuesFunction();
|
|
|
|
NS_NAMED_LITERAL_CSTRING(functionName, "upgrade_idv");
|
|
|
|
nsresult rv = aConnection->CreateFunction(functionName, 1, function);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"UPDATE object_data "
|
|
"SET index_data_values = upgrade_idv(index_data_values) "
|
|
"WHERE index_data_values IS NOT NULL;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->RemoveFunction(functionName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(21, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom21_0To22_0(mozIStorageConnection* aConnection)
|
|
{
|
|
// The only change between 21 and 22 was a different structured clone format,
|
|
// but it's backwards-compatible.
|
|
nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(22, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom22_0To23_0(mozIStorageConnection* aConnection,
|
|
const nsACString& aOrigin)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
MOZ_ASSERT(!aOrigin.IsEmpty());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom22_0To23_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"UPDATE database "
|
|
"SET origin = :origin;"
|
|
), 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->SetSchemaVersion(MakeSchemaVersion(23, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom23_0To24_0(mozIStorageConnection* aConnection)
|
|
{
|
|
// The only change between 23 and 24 was a different structured clone format,
|
|
// but it's backwards-compatible.
|
|
nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(24, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom24_0To25_0(mozIStorageConnection* aConnection)
|
|
{
|
|
// The changes between 24 and 25 were an upgraded snappy library, a different
|
|
// structured clone format and a different file_ds format. But everything is
|
|
// backwards-compatible.
|
|
nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(25, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
GetDatabaseFileURL(nsIFile* aDatabaseFile,
|
|
PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
uint32_t aTelemetryId,
|
|
nsIFileURL** aResult)
|
|
{
|
|
MOZ_ASSERT(aDatabaseFile);
|
|
MOZ_ASSERT(aResult);
|
|
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIProtocolHandler> protocolHandler(
|
|
do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file", &rv));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFileProtocolHandler> fileHandler(
|
|
do_QueryInterface(protocolHandler, &rv));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
rv = fileHandler->NewFileURI(aDatabaseFile, getter_AddRefs(uri));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFileURL> 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<mozIStorageStatement> 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 <class FileOrURLType>
|
|
struct StorageOpenTraits;
|
|
|
|
template <>
|
|
struct StorageOpenTraits<nsIFileURL*>
|
|
{
|
|
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_SUCCEEDS(aFileURL->GetFileName(aPath));
|
|
}
|
|
#endif
|
|
};
|
|
|
|
template <>
|
|
struct StorageOpenTraits<nsIFile*>
|
|
{
|
|
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_SUCCEEDS(aFile->GetPath(path));
|
|
|
|
aPath.AssignWithConversion(path);
|
|
}
|
|
#endif
|
|
};
|
|
|
|
template <template <class> class SmartPtr, class FileOrURLType>
|
|
struct StorageOpenTraits<SmartPtr<FileOrURLType>>
|
|
: public StorageOpenTraits<FileOrURLType*>
|
|
{ };
|
|
|
|
template <class FileOrURLType>
|
|
nsresult
|
|
OpenDatabaseAndHandleBusy(mozIStorageService* aStorageService,
|
|
FileOrURLType aFileOrURL,
|
|
mozIStorageConnection** aConnection)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aStorageService);
|
|
MOZ_ASSERT(aFileOrURL);
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
nsCOMPtr<mozIStorageConnection> connection;
|
|
nsresult rv =
|
|
StorageOpenTraits<FileOrURLType>::Open(aStorageService,
|
|
aFileOrURL,
|
|
getter_AddRefs(connection));
|
|
|
|
if (rv == NS_ERROR_STORAGE_BUSY) {
|
|
#ifdef DEBUG
|
|
{
|
|
nsCString path;
|
|
StorageOpenTraits<FileOrURLType>::GetPath(aFileOrURL, path);
|
|
|
|
nsPrintfCString message("Received NS_ERROR_STORAGE_BUSY when attempting "
|
|
"to open database '%s', retrying for up to 10 "
|
|
"seconds",
|
|
path.get());
|
|
NS_WARNING(message.get());
|
|
}
|
|
#endif
|
|
|
|
// Another thread must be checkpointing the WAL. Wait up to 10 seconds for
|
|
// that to complete.
|
|
TimeStamp start = TimeStamp::NowLoRes();
|
|
|
|
while (true) {
|
|
PR_Sleep(PR_MillisecondsToInterval(100));
|
|
|
|
rv = StorageOpenTraits<FileOrURLType>::Open(aStorageService,
|
|
aFileOrURL,
|
|
getter_AddRefs(connection));
|
|
if (rv != NS_ERROR_STORAGE_BUSY ||
|
|
TimeStamp::NowLoRes() - start > TimeDuration::FromSeconds(10)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
connection.forget(aConnection);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CreateStorageConnection(nsIFile* aDBFile,
|
|
nsIFile* aFMDirectory,
|
|
const nsAString& aName,
|
|
PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
uint32_t aTelemetryId,
|
|
mozIStorageConnection** aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aDBFile);
|
|
MOZ_ASSERT(aFMDirectory);
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CreateStorageConnection",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv;
|
|
bool exists;
|
|
|
|
if (IndexedDatabaseManager::InLowDiskSpaceMode()) {
|
|
rv = aDBFile->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!exists) {
|
|
NS_WARNING("Refusing to create database because disk space is low!");
|
|
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIFileURL> dbFileUrl;
|
|
rv = GetDatabaseFileURL(aDBFile,
|
|
aPersistenceType,
|
|
aGroup,
|
|
aOrigin,
|
|
aTelemetryId,
|
|
getter_AddRefs(dbFileUrl));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageService> ss =
|
|
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageConnection> connection;
|
|
rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
|
|
if (rv == NS_ERROR_FILE_CORRUPTED) {
|
|
// If we're just opening the database during origin initialization, then
|
|
// we don't want to erase any files. The failure here will fail origin
|
|
// initialization too.
|
|
if (aName.IsVoid()) {
|
|
return rv;
|
|
}
|
|
|
|
// Nuke the database file.
|
|
rv = aDBFile->Remove(false);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aFMDirectory->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (exists) {
|
|
bool isDirectory;
|
|
rv = aFMDirectory->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
if (NS_WARN_IF(!isDirectory)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
rv = aFMDirectory->Remove(true);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = SetDefaultPragmas(connection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = connection->EnableModule(NS_LITERAL_CSTRING("filesystem"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Check to make sure that the database schema is correct.
|
|
int32_t schemaVersion;
|
|
rv = connection->GetSchemaVersion(&schemaVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Unknown schema will fail origin initialization too.
|
|
if (!schemaVersion && aName.IsVoid()) {
|
|
IDB_WARNING("Unable to open IndexedDB database, schema is not set!");
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
if (schemaVersion > kSQLiteSchemaVersion) {
|
|
IDB_WARNING("Unable to open IndexedDB database, schema is too high!");
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
bool journalModeSet = false;
|
|
|
|
if (schemaVersion != kSQLiteSchemaVersion) {
|
|
const bool newDatabase = !schemaVersion;
|
|
|
|
if (newDatabase) {
|
|
// Set the page size first.
|
|
if (kSQLitePageSizeOverride) {
|
|
rv = connection->ExecuteSimpleSQL(
|
|
nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride)
|
|
);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// We have to set the auto_vacuum mode before opening a transaction.
|
|
rv = connection->ExecuteSimpleSQL(
|
|
#ifdef IDB_MOBILE
|
|
// Turn on full auto_vacuum mode to reclaim disk space on mobile
|
|
// devices (at the cost of some COMMIT speed).
|
|
NS_LITERAL_CSTRING("PRAGMA auto_vacuum = FULL;")
|
|
#else
|
|
// Turn on incremental auto_vacuum mode on desktop builds.
|
|
NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL;")
|
|
#endif
|
|
);
|
|
if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
|
|
// mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE,
|
|
// which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
|
|
rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = SetJournalMode(connection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
journalModeSet = true;
|
|
} else {
|
|
#ifdef DEBUG
|
|
// Disable foreign key support while upgrading. This has to be done before
|
|
// starting a transaction.
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
connection->ExecuteSimpleSQL(
|
|
NS_LITERAL_CSTRING("PRAGMA foreign_keys = OFF;")));
|
|
#endif
|
|
}
|
|
|
|
bool vacuumNeeded = false;
|
|
|
|
mozStorageTransaction transaction(connection, false,
|
|
mozIStorageConnection::TRANSACTION_IMMEDIATE);
|
|
|
|
if (newDatabase) {
|
|
rv = CreateTables(connection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)));
|
|
MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
nsresult rv = connection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"INSERT INTO database (name, origin) "
|
|
"VALUES (:name, :origin)"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName);
|
|
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;
|
|
}
|
|
} else {
|
|
// This logic needs to change next time we change the schema!
|
|
static_assert(kSQLiteSchemaVersion == int32_t((25 << 4) + 0),
|
|
"Upgrade function needed due to schema version increase.");
|
|
|
|
while (schemaVersion != kSQLiteSchemaVersion) {
|
|
if (schemaVersion == 4) {
|
|
rv = UpgradeSchemaFrom4To5(connection);
|
|
} else if (schemaVersion == 5) {
|
|
rv = UpgradeSchemaFrom5To6(connection);
|
|
} else if (schemaVersion == 6) {
|
|
rv = UpgradeSchemaFrom6To7(connection);
|
|
} else if (schemaVersion == 7) {
|
|
rv = UpgradeSchemaFrom7To8(connection);
|
|
} else if (schemaVersion == 8) {
|
|
rv = UpgradeSchemaFrom8To9_0(connection);
|
|
vacuumNeeded = true;
|
|
} else if (schemaVersion == MakeSchemaVersion(9, 0)) {
|
|
rv = UpgradeSchemaFrom9_0To10_0(connection);
|
|
} else if (schemaVersion == MakeSchemaVersion(10, 0)) {
|
|
rv = UpgradeSchemaFrom10_0To11_0(connection);
|
|
} else if (schemaVersion == MakeSchemaVersion(11, 0)) {
|
|
rv = UpgradeSchemaFrom11_0To12_0(connection);
|
|
} else if (schemaVersion == MakeSchemaVersion(12, 0)) {
|
|
rv = UpgradeSchemaFrom12_0To13_0(connection, &vacuumNeeded);
|
|
} else if (schemaVersion == MakeSchemaVersion(13, 0)) {
|
|
rv = UpgradeSchemaFrom13_0To14_0(connection);
|
|
} else if (schemaVersion == MakeSchemaVersion(14, 0)) {
|
|
rv = UpgradeSchemaFrom14_0To15_0(connection);
|
|
} else if (schemaVersion == MakeSchemaVersion(15, 0)) {
|
|
rv = UpgradeSchemaFrom15_0To16_0(connection);
|
|
} else if (schemaVersion == MakeSchemaVersion(16, 0)) {
|
|
rv = UpgradeSchemaFrom16_0To17_0(connection);
|
|
} else if (schemaVersion == MakeSchemaVersion(17, 0)) {
|
|
rv = UpgradeSchemaFrom17_0To18_0(connection, aOrigin);
|
|
vacuumNeeded = true;
|
|
} else if (schemaVersion == MakeSchemaVersion(18, 0)) {
|
|
rv = UpgradeSchemaFrom18_0To19_0(connection);
|
|
} else if (schemaVersion == MakeSchemaVersion(19, 0)) {
|
|
rv = UpgradeSchemaFrom19_0To20_0(aFMDirectory, connection);
|
|
} else if (schemaVersion == MakeSchemaVersion(20, 0)) {
|
|
rv = UpgradeSchemaFrom20_0To21_0(connection);
|
|
} else if (schemaVersion == MakeSchemaVersion(21, 0)) {
|
|
rv = UpgradeSchemaFrom21_0To22_0(connection);
|
|
} else if (schemaVersion == MakeSchemaVersion(22, 0)) {
|
|
rv = UpgradeSchemaFrom22_0To23_0(connection, aOrigin);
|
|
} else if (schemaVersion == MakeSchemaVersion(23, 0)) {
|
|
rv = UpgradeSchemaFrom23_0To24_0(connection);
|
|
} else if (schemaVersion == MakeSchemaVersion(24, 0)) {
|
|
rv = UpgradeSchemaFrom24_0To25_0(connection);
|
|
} else {
|
|
IDB_WARNING("Unable to open IndexedDB database, no upgrade path is "
|
|
"available!");
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = connection->GetSchemaVersion(&schemaVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
|
|
}
|
|
|
|
rv = transaction.Commit();
|
|
if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
|
|
// mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE,
|
|
// which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
|
|
rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (!newDatabase) {
|
|
// Re-enable foreign key support after doing a foreign key check.
|
|
nsCOMPtr<mozIStorageStatement> checkStmt;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
connection->CreateStatement(
|
|
NS_LITERAL_CSTRING("PRAGMA foreign_key_check;"),
|
|
getter_AddRefs(checkStmt)));
|
|
|
|
bool hasResult;
|
|
MOZ_ALWAYS_SUCCEEDS(checkStmt->ExecuteStep(&hasResult));
|
|
MOZ_ASSERT(!hasResult, "Database has inconsisistent foreign keys!");
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
connection->ExecuteSimpleSQL(
|
|
NS_LITERAL_CSTRING("PRAGMA foreign_keys = OFF;")));
|
|
}
|
|
#endif
|
|
|
|
if (kSQLitePageSizeOverride && !newDatabase) {
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = connection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"PRAGMA page_size;"
|
|
), 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);
|
|
|
|
int32_t pageSize;
|
|
rv = stmt->GetInt32(0, &pageSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536);
|
|
|
|
if (kSQLitePageSizeOverride != uint32_t(pageSize)) {
|
|
// We must not be in WAL journal mode to change the page size.
|
|
rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"PRAGMA journal_mode = DELETE;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = connection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"PRAGMA journal_mode;"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
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.EqualsLiteral("delete")) {
|
|
// Successfully set to rollback journal mode so changing the page size
|
|
// is possible with a VACUUM.
|
|
rv = connection->ExecuteSimpleSQL(
|
|
nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride)
|
|
);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// We will need to VACUUM in order to change the page size.
|
|
vacuumNeeded = true;
|
|
} else {
|
|
NS_WARNING("Failed to set journal_mode for database, unable to "
|
|
"change the page size!");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (vacuumNeeded) {
|
|
rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (newDatabase || vacuumNeeded) {
|
|
if (journalModeSet) {
|
|
// Make sure we checkpoint to get an accurate file size.
|
|
rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"PRAGMA wal_checkpoint(FULL);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
int64_t fileSize;
|
|
rv = aDBFile->GetFileSize(&fileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(fileSize > 0);
|
|
|
|
PRTime vacuumTime = PR_Now();
|
|
MOZ_ASSERT(vacuumTime);
|
|
|
|
nsCOMPtr<mozIStorageStatement> vacuumTimeStmt;
|
|
rv = connection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"UPDATE database "
|
|
"SET last_vacuum_time = :time"
|
|
", last_vacuum_size = :size;"
|
|
), getter_AddRefs(vacuumTimeStmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("time"),
|
|
vacuumTime);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("size"),
|
|
fileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = vacuumTimeStmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!journalModeSet) {
|
|
rv = SetJournalMode(connection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
connection.forget(aConnection);
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<nsIFile>
|
|
GetFileForPath(const nsAString& aPath)
|
|
{
|
|
MOZ_ASSERT(!aPath.IsEmpty());
|
|
|
|
nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
|
|
if (NS_WARN_IF(!file)) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(file->InitWithPath(aPath)))) {
|
|
return nullptr;
|
|
}
|
|
|
|
return file.forget();
|
|
}
|
|
|
|
nsresult
|
|
GetStorageConnection(nsIFile* aDatabaseFile,
|
|
PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
uint32_t aTelemetryId,
|
|
mozIStorageConnection** aConnection)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aDatabaseFile);
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"GetStorageConnection",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
bool exists;
|
|
nsresult rv = aDatabaseFile->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!exists)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
nsCOMPtr<nsIFileURL> dbFileUrl;
|
|
rv = GetDatabaseFileURL(aDatabaseFile,
|
|
aPersistenceType,
|
|
aGroup,
|
|
aOrigin,
|
|
aTelemetryId,
|
|
getter_AddRefs(dbFileUrl));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageService> ss =
|
|
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageConnection> connection;
|
|
rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = SetDefaultPragmas(connection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = SetJournalMode(connection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
connection.forget(aConnection);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
GetStorageConnection(const nsAString& aDatabaseFilePath,
|
|
PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
uint32_t aTelemetryId,
|
|
mozIStorageConnection** aConnection)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(!aDatabaseFilePath.IsEmpty());
|
|
MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, NS_LITERAL_STRING(".sqlite")));
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
nsCOMPtr<nsIFile> dbFile = GetFileForPath(aDatabaseFilePath);
|
|
if (NS_WARN_IF(!dbFile)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
return GetStorageConnection(dbFile,
|
|
aPersistenceType,
|
|
aGroup,
|
|
aOrigin,
|
|
aTelemetryId,
|
|
aConnection);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* ConnectionPool declarations
|
|
******************************************************************************/
|
|
|
|
class DatabaseConnection final
|
|
{
|
|
friend class ConnectionPool;
|
|
|
|
enum class CheckpointMode
|
|
{
|
|
Full,
|
|
Restart,
|
|
Truncate
|
|
};
|
|
|
|
public:
|
|
class AutoSavepoint;
|
|
class CachedStatement;
|
|
class UpdateRefcountFunction;
|
|
|
|
private:
|
|
nsCOMPtr<mozIStorageConnection> mStorageConnection;
|
|
RefPtr<FileManager> mFileManager;
|
|
nsInterfaceHashtable<nsCStringHashKey, mozIStorageStatement>
|
|
mCachedStatements;
|
|
RefPtr<UpdateRefcountFunction> mUpdateRefcountFunction;
|
|
RefPtr<QuotaObject> mQuotaObject;
|
|
RefPtr<QuotaObject> mJournalQuotaObject;
|
|
bool mInReadTransaction;
|
|
bool mInWriteTransaction;
|
|
|
|
#ifdef DEBUG
|
|
uint32_t mDEBUGSavepointCount;
|
|
PRThread* mDEBUGThread;
|
|
#endif
|
|
|
|
public:
|
|
void
|
|
AssertIsOnConnectionThread() const
|
|
{
|
|
MOZ_ASSERT(mDEBUGThread);
|
|
MOZ_ASSERT(PR_GetCurrentThread() == mDEBUGThread);
|
|
}
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DatabaseConnection)
|
|
|
|
mozIStorageConnection*
|
|
GetStorageConnection() const
|
|
{
|
|
if (mStorageConnection) {
|
|
AssertIsOnConnectionThread();
|
|
return mStorageConnection;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
UpdateRefcountFunction*
|
|
GetUpdateRefcountFunction() const
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
|
|
return mUpdateRefcountFunction;
|
|
}
|
|
|
|
nsresult
|
|
GetCachedStatement(const nsACString& aQuery,
|
|
CachedStatement* aCachedStatement);
|
|
|
|
nsresult
|
|
BeginWriteTransaction();
|
|
|
|
nsresult
|
|
CommitWriteTransaction();
|
|
|
|
void
|
|
RollbackWriteTransaction();
|
|
|
|
void
|
|
FinishWriteTransaction();
|
|
|
|
nsresult
|
|
StartSavepoint();
|
|
|
|
nsresult
|
|
ReleaseSavepoint();
|
|
|
|
nsresult
|
|
RollbackSavepoint();
|
|
|
|
nsresult
|
|
Checkpoint()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
|
|
return CheckpointInternal(CheckpointMode::Full);
|
|
}
|
|
|
|
void
|
|
DoIdleProcessing(bool aNeedsCheckpoint);
|
|
|
|
void
|
|
Close();
|
|
|
|
nsresult
|
|
DisableQuotaChecks();
|
|
|
|
void
|
|
EnableQuotaChecks();
|
|
|
|
private:
|
|
DatabaseConnection(mozIStorageConnection* aStorageConnection,
|
|
FileManager* aFileManager);
|
|
|
|
~DatabaseConnection();
|
|
|
|
nsresult
|
|
Init();
|
|
|
|
nsresult
|
|
CheckpointInternal(CheckpointMode aMode);
|
|
|
|
nsresult
|
|
GetFreelistCount(CachedStatement& aCachedStatement, uint32_t* aFreelistCount);
|
|
|
|
nsresult
|
|
ReclaimFreePagesWhileIdle(CachedStatement& aFreelistStatement,
|
|
CachedStatement& aRollbackStatement,
|
|
uint32_t aFreelistCount,
|
|
bool aNeedsCheckpoint,
|
|
bool* aFreedSomePages);
|
|
|
|
nsresult
|
|
GetFileSize(const nsAString& aPath, int64_t* aResult);
|
|
};
|
|
|
|
class MOZ_STACK_CLASS DatabaseConnection::AutoSavepoint final
|
|
{
|
|
DatabaseConnection* mConnection;
|
|
#ifdef DEBUG
|
|
const TransactionBase* mDEBUGTransaction;
|
|
#endif
|
|
|
|
public:
|
|
AutoSavepoint();
|
|
~AutoSavepoint();
|
|
|
|
nsresult
|
|
Start(const TransactionBase* aTransaction);
|
|
|
|
nsresult
|
|
Commit();
|
|
};
|
|
|
|
class DatabaseConnection::CachedStatement final
|
|
{
|
|
friend class DatabaseConnection;
|
|
|
|
nsCOMPtr<mozIStorageStatement> mStatement;
|
|
Maybe<mozStorageStatementScoper> mScoper;
|
|
|
|
#ifdef DEBUG
|
|
DatabaseConnection* mDEBUGConnection;
|
|
#endif
|
|
|
|
public:
|
|
CachedStatement();
|
|
~CachedStatement();
|
|
|
|
void
|
|
AssertIsOnConnectionThread() const
|
|
{
|
|
#ifdef DEBUG
|
|
if (mDEBUGConnection) {
|
|
mDEBUGConnection->AssertIsOnConnectionThread();
|
|
}
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
#endif
|
|
}
|
|
|
|
operator mozIStorageStatement*() const;
|
|
|
|
mozIStorageStatement*
|
|
operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN;
|
|
|
|
void
|
|
Reset();
|
|
|
|
private:
|
|
// Only called by DatabaseConnection.
|
|
void
|
|
Assign(DatabaseConnection* aConnection,
|
|
already_AddRefed<mozIStorageStatement> aStatement);
|
|
|
|
// No funny business allowed.
|
|
CachedStatement(const CachedStatement&) = delete;
|
|
CachedStatement& operator=(const CachedStatement&) = delete;
|
|
};
|
|
|
|
class DatabaseConnection::UpdateRefcountFunction final
|
|
: public mozIStorageFunction
|
|
{
|
|
class DatabaseUpdateFunction;
|
|
class FileInfoEntry;
|
|
|
|
enum class UpdateType
|
|
{
|
|
Increment,
|
|
Decrement
|
|
};
|
|
|
|
DatabaseConnection* mConnection;
|
|
FileManager* mFileManager;
|
|
nsClassHashtable<nsUint64HashKey, FileInfoEntry> mFileInfoEntries;
|
|
nsDataHashtable<nsUint64HashKey, FileInfoEntry*> mSavepointEntriesIndex;
|
|
|
|
nsTArray<int64_t> mJournalsToCreateBeforeCommit;
|
|
nsTArray<int64_t> mJournalsToRemoveAfterCommit;
|
|
nsTArray<int64_t> mJournalsToRemoveAfterAbort;
|
|
|
|
bool mInSavepoint;
|
|
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_MOZISTORAGEFUNCTION
|
|
|
|
UpdateRefcountFunction(DatabaseConnection* aConnection,
|
|
FileManager* aFileManager);
|
|
|
|
nsresult
|
|
WillCommit();
|
|
|
|
void
|
|
DidCommit();
|
|
|
|
void
|
|
DidAbort();
|
|
|
|
void
|
|
StartSavepoint();
|
|
|
|
void
|
|
ReleaseSavepoint();
|
|
|
|
void
|
|
RollbackSavepoint();
|
|
|
|
void
|
|
Reset();
|
|
|
|
private:
|
|
~UpdateRefcountFunction() = default;
|
|
|
|
nsresult
|
|
ProcessValue(mozIStorageValueArray* aValues,
|
|
int32_t aIndex,
|
|
UpdateType aUpdateType);
|
|
|
|
nsresult
|
|
CreateJournals();
|
|
|
|
nsresult
|
|
RemoveJournals(const nsTArray<int64_t>& aJournals);
|
|
};
|
|
|
|
class DatabaseConnection::UpdateRefcountFunction::DatabaseUpdateFunction final
|
|
{
|
|
CachedStatement mUpdateStatement;
|
|
CachedStatement mSelectStatement;
|
|
CachedStatement mInsertStatement;
|
|
|
|
UpdateRefcountFunction* mFunction;
|
|
|
|
nsresult mErrorCode;
|
|
|
|
public:
|
|
explicit
|
|
DatabaseUpdateFunction(UpdateRefcountFunction* aFunction)
|
|
: mFunction(aFunction)
|
|
, mErrorCode(NS_OK)
|
|
{
|
|
MOZ_COUNT_CTOR(
|
|
DatabaseConnection::UpdateRefcountFunction::DatabaseUpdateFunction);
|
|
}
|
|
|
|
~DatabaseUpdateFunction()
|
|
{
|
|
MOZ_COUNT_DTOR(
|
|
DatabaseConnection::UpdateRefcountFunction::DatabaseUpdateFunction);
|
|
}
|
|
|
|
bool
|
|
Update(int64_t aId, int32_t aDelta);
|
|
|
|
nsresult
|
|
ErrorCode() const
|
|
{
|
|
return mErrorCode;
|
|
}
|
|
|
|
private:
|
|
nsresult
|
|
UpdateInternal(int64_t aId, int32_t aDelta);
|
|
};
|
|
|
|
class DatabaseConnection::UpdateRefcountFunction::FileInfoEntry final
|
|
{
|
|
friend class UpdateRefcountFunction;
|
|
|
|
RefPtr<FileInfo> mFileInfo;
|
|
int32_t mDelta;
|
|
int32_t mSavepointDelta;
|
|
|
|
public:
|
|
explicit
|
|
FileInfoEntry(FileInfo* aFileInfo)
|
|
: mFileInfo(aFileInfo)
|
|
, mDelta(0)
|
|
, mSavepointDelta(0)
|
|
{
|
|
MOZ_COUNT_CTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
|
|
}
|
|
|
|
~FileInfoEntry()
|
|
{
|
|
MOZ_COUNT_DTOR(DatabaseConnection::UpdateRefcountFunction::FileInfoEntry);
|
|
}
|
|
};
|
|
|
|
class ConnectionPool final
|
|
{
|
|
public:
|
|
class FinishCallback;
|
|
|
|
private:
|
|
class ConnectionRunnable;
|
|
class CloseConnectionRunnable;
|
|
struct DatabaseInfo;
|
|
struct DatabasesCompleteCallback;
|
|
class FinishCallbackWrapper;
|
|
class IdleConnectionRunnable;
|
|
struct IdleDatabaseInfo;
|
|
struct IdleResource;
|
|
struct IdleThreadInfo;
|
|
struct ThreadInfo;
|
|
class ThreadRunnable;
|
|
class TransactionInfo;
|
|
struct TransactionInfoPair;
|
|
|
|
// This mutex guards mDatabases, see below.
|
|
Mutex mDatabasesMutex;
|
|
|
|
nsTArray<IdleThreadInfo> mIdleThreads;
|
|
nsTArray<IdleDatabaseInfo> mIdleDatabases;
|
|
nsTArray<DatabaseInfo*> mDatabasesPerformingIdleMaintenance;
|
|
nsCOMPtr<nsITimer> mIdleTimer;
|
|
TimeStamp mTargetIdleTime;
|
|
|
|
// Only modifed on the owning thread, but read on multiple threads. Therefore
|
|
// all modifications and all reads off the owning thread must be protected by
|
|
// mDatabasesMutex.
|
|
nsClassHashtable<nsCStringHashKey, DatabaseInfo> mDatabases;
|
|
|
|
nsClassHashtable<nsUint64HashKey, TransactionInfo> mTransactions;
|
|
nsTArray<TransactionInfo*> mQueuedTransactions;
|
|
|
|
nsTArray<nsAutoPtr<DatabasesCompleteCallback>> mCompleteCallbacks;
|
|
|
|
uint64_t mNextTransactionId;
|
|
uint32_t mTotalThreadCount;
|
|
bool mShutdownRequested;
|
|
bool mShutdownComplete;
|
|
|
|
#ifdef DEBUG
|
|
PRThread* mDEBUGOwningThread;
|
|
#endif
|
|
|
|
public:
|
|
ConnectionPool();
|
|
|
|
void
|
|
AssertIsOnOwningThread() const
|
|
#ifdef DEBUG
|
|
;
|
|
#else
|
|
{ }
|
|
#endif
|
|
|
|
nsresult
|
|
GetOrCreateConnection(const Database* aDatabase,
|
|
DatabaseConnection** aConnection);
|
|
|
|
uint64_t
|
|
Start(const nsID& aBackgroundChildLoggingId,
|
|
const nsACString& aDatabaseId,
|
|
int64_t aLoggingSerialNumber,
|
|
const nsTArray<nsString>& aObjectStoreNames,
|
|
bool aIsWriteTransaction,
|
|
TransactionDatabaseOperationBase* aTransactionOp);
|
|
|
|
void
|
|
Dispatch(uint64_t aTransactionId, nsIRunnable* aRunnable);
|
|
|
|
void
|
|
Finish(uint64_t aTransactionId, FinishCallback* aCallback);
|
|
|
|
void
|
|
CloseDatabaseWhenIdle(const nsACString& aDatabaseId)
|
|
{
|
|
Unused << CloseDatabaseWhenIdleInternal(aDatabaseId);
|
|
}
|
|
|
|
void
|
|
WaitForDatabasesToComplete(nsTArray<nsCString>&& aDatabaseIds,
|
|
nsIRunnable* aCallback);
|
|
|
|
void
|
|
Shutdown();
|
|
|
|
NS_INLINE_DECL_REFCOUNTING(ConnectionPool)
|
|
|
|
private:
|
|
~ConnectionPool();
|
|
|
|
static void
|
|
IdleTimerCallback(nsITimer* aTimer, void* aClosure);
|
|
|
|
void
|
|
Cleanup();
|
|
|
|
void
|
|
AdjustIdleTimer();
|
|
|
|
void
|
|
CancelIdleTimer();
|
|
|
|
void
|
|
ShutdownThread(ThreadInfo& aThreadInfo);
|
|
|
|
void
|
|
CloseIdleDatabases();
|
|
|
|
void
|
|
ShutdownIdleThreads();
|
|
|
|
bool
|
|
ScheduleTransaction(TransactionInfo* aTransactionInfo,
|
|
bool aFromQueuedTransactions);
|
|
|
|
void
|
|
NoteFinishedTransaction(uint64_t aTransactionId);
|
|
|
|
void
|
|
ScheduleQueuedTransactions(ThreadInfo& aThreadInfo);
|
|
|
|
void
|
|
NoteIdleDatabase(DatabaseInfo* aDatabaseInfo);
|
|
|
|
void
|
|
NoteClosedDatabase(DatabaseInfo* aDatabaseInfo);
|
|
|
|
bool
|
|
MaybeFireCallback(DatabasesCompleteCallback* aCallback);
|
|
|
|
void
|
|
PerformIdleDatabaseMaintenance(DatabaseInfo* aDatabaseInfo);
|
|
|
|
void
|
|
CloseDatabase(DatabaseInfo* aDatabaseInfo);
|
|
|
|
bool
|
|
CloseDatabaseWhenIdleInternal(const nsACString& aDatabaseId);
|
|
};
|
|
|
|
class ConnectionPool::ConnectionRunnable
|
|
: public Runnable
|
|
{
|
|
protected:
|
|
DatabaseInfo* mDatabaseInfo;
|
|
nsCOMPtr<nsIEventTarget> mOwningThread;
|
|
|
|
explicit
|
|
ConnectionRunnable(DatabaseInfo* aDatabaseInfo);
|
|
|
|
~ConnectionRunnable() override = default;
|
|
};
|
|
|
|
class ConnectionPool::IdleConnectionRunnable final
|
|
: public ConnectionRunnable
|
|
{
|
|
bool mNeedsCheckpoint;
|
|
|
|
public:
|
|
IdleConnectionRunnable(DatabaseInfo* aDatabaseInfo, bool aNeedsCheckpoint)
|
|
: ConnectionRunnable(aDatabaseInfo)
|
|
, mNeedsCheckpoint(aNeedsCheckpoint)
|
|
{ }
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
private:
|
|
~IdleConnectionRunnable() override = default;
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
};
|
|
|
|
class ConnectionPool::CloseConnectionRunnable final
|
|
: public ConnectionRunnable
|
|
{
|
|
public:
|
|
explicit
|
|
CloseConnectionRunnable(DatabaseInfo* aDatabaseInfo)
|
|
: ConnectionRunnable(aDatabaseInfo)
|
|
{ }
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
private:
|
|
~CloseConnectionRunnable() override = default;
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
};
|
|
|
|
struct ConnectionPool::ThreadInfo
|
|
{
|
|
nsCOMPtr<nsIThread> mThread;
|
|
RefPtr<ThreadRunnable> mRunnable;
|
|
|
|
ThreadInfo();
|
|
|
|
explicit
|
|
ThreadInfo(const ThreadInfo& aOther);
|
|
|
|
~ThreadInfo();
|
|
};
|
|
|
|
struct ConnectionPool::DatabaseInfo final
|
|
{
|
|
friend class nsAutoPtr<DatabaseInfo>;
|
|
|
|
RefPtr<ConnectionPool> mConnectionPool;
|
|
const nsCString mDatabaseId;
|
|
RefPtr<DatabaseConnection> mConnection;
|
|
nsClassHashtable<nsStringHashKey, TransactionInfoPair> mBlockingTransactions;
|
|
nsTArray<TransactionInfo*> mTransactionsScheduledDuringClose;
|
|
nsTArray<TransactionInfo*> mScheduledWriteTransactions;
|
|
TransactionInfo* mRunningWriteTransaction;
|
|
ThreadInfo mThreadInfo;
|
|
uint32_t mReadTransactionCount;
|
|
uint32_t mWriteTransactionCount;
|
|
bool mNeedsCheckpoint;
|
|
bool mIdle;
|
|
bool mCloseOnIdle;
|
|
bool mClosing;
|
|
|
|
#ifdef DEBUG
|
|
PRThread* mDEBUGConnectionThread;
|
|
#endif
|
|
|
|
DatabaseInfo(ConnectionPool* aConnectionPool,
|
|
const nsACString& aDatabaseId);
|
|
|
|
void
|
|
AssertIsOnConnectionThread() const
|
|
{
|
|
MOZ_ASSERT(mDEBUGConnectionThread);
|
|
MOZ_ASSERT(PR_GetCurrentThread() == mDEBUGConnectionThread);
|
|
}
|
|
|
|
uint64_t
|
|
TotalTransactionCount() const
|
|
{
|
|
return mReadTransactionCount + mWriteTransactionCount;
|
|
}
|
|
|
|
private:
|
|
~DatabaseInfo();
|
|
|
|
DatabaseInfo(const DatabaseInfo&) = delete;
|
|
DatabaseInfo& operator=(const DatabaseInfo&) = delete;
|
|
};
|
|
|
|
struct ConnectionPool::DatabasesCompleteCallback final
|
|
{
|
|
friend class nsAutoPtr<DatabasesCompleteCallback>;
|
|
|
|
nsTArray<nsCString> mDatabaseIds;
|
|
nsCOMPtr<nsIRunnable> mCallback;
|
|
|
|
DatabasesCompleteCallback(nsTArray<nsCString>&& aDatabaseIds,
|
|
nsIRunnable* aCallback);
|
|
|
|
private:
|
|
~DatabasesCompleteCallback();
|
|
};
|
|
|
|
class NS_NO_VTABLE ConnectionPool::FinishCallback
|
|
: public nsIRunnable
|
|
{
|
|
public:
|
|
// Called on the owning thread before any additional transactions are
|
|
// unblocked.
|
|
virtual void
|
|
TransactionFinishedBeforeUnblock() = 0;
|
|
|
|
// Called on the owning thread after additional transactions may have been
|
|
// unblocked.
|
|
virtual void
|
|
TransactionFinishedAfterUnblock() = 0;
|
|
|
|
protected:
|
|
FinishCallback()
|
|
{ }
|
|
|
|
virtual ~FinishCallback() = default;
|
|
};
|
|
|
|
class ConnectionPool::FinishCallbackWrapper final
|
|
: public Runnable
|
|
{
|
|
RefPtr<ConnectionPool> mConnectionPool;
|
|
RefPtr<FinishCallback> mCallback;
|
|
nsCOMPtr<nsIEventTarget> mOwningThread;
|
|
uint64_t mTransactionId;
|
|
bool mHasRunOnce;
|
|
|
|
public:
|
|
FinishCallbackWrapper(ConnectionPool* aConnectionPool,
|
|
uint64_t aTransactionId,
|
|
FinishCallback* aCallback);
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
private:
|
|
~FinishCallbackWrapper() override;
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
};
|
|
|
|
struct ConnectionPool::IdleResource
|
|
{
|
|
TimeStamp mIdleTime;
|
|
|
|
protected:
|
|
explicit
|
|
IdleResource(const TimeStamp& aIdleTime);
|
|
|
|
explicit
|
|
IdleResource(const IdleResource& aOther) = delete;
|
|
|
|
~IdleResource();
|
|
};
|
|
|
|
struct ConnectionPool::IdleDatabaseInfo final
|
|
: public IdleResource
|
|
{
|
|
DatabaseInfo* mDatabaseInfo;
|
|
|
|
public:
|
|
MOZ_IMPLICIT
|
|
IdleDatabaseInfo(DatabaseInfo* aDatabaseInfo);
|
|
|
|
explicit
|
|
IdleDatabaseInfo(const IdleDatabaseInfo& aOther) = delete;
|
|
|
|
~IdleDatabaseInfo();
|
|
|
|
bool
|
|
operator==(const IdleDatabaseInfo& aOther) const
|
|
{
|
|
return mDatabaseInfo == aOther.mDatabaseInfo;
|
|
}
|
|
|
|
bool
|
|
operator<(const IdleDatabaseInfo& aOther) const
|
|
{
|
|
return mIdleTime < aOther.mIdleTime;
|
|
}
|
|
};
|
|
|
|
struct ConnectionPool::IdleThreadInfo final
|
|
: public IdleResource
|
|
{
|
|
ThreadInfo mThreadInfo;
|
|
|
|
public:
|
|
// Boo, this is needed because nsTArray::InsertElementSorted() doesn't yet
|
|
// work with rvalue references.
|
|
MOZ_IMPLICIT
|
|
IdleThreadInfo(const ThreadInfo& aThreadInfo);
|
|
|
|
explicit
|
|
IdleThreadInfo(const IdleThreadInfo& aOther) = delete;
|
|
|
|
~IdleThreadInfo();
|
|
|
|
bool
|
|
operator==(const IdleThreadInfo& aOther) const
|
|
{
|
|
return mThreadInfo.mRunnable == aOther.mThreadInfo.mRunnable &&
|
|
mThreadInfo.mThread == aOther.mThreadInfo.mThread;
|
|
}
|
|
|
|
bool
|
|
operator<(const IdleThreadInfo& aOther) const
|
|
{
|
|
return mIdleTime < aOther.mIdleTime;
|
|
}
|
|
};
|
|
|
|
class ConnectionPool::ThreadRunnable final
|
|
: public Runnable
|
|
{
|
|
// Only touched on the background thread.
|
|
static uint32_t sNextSerialNumber;
|
|
|
|
// Set at construction for logging.
|
|
const uint32_t mSerialNumber;
|
|
|
|
// These two values are only modified on the connection thread.
|
|
bool mFirstRun;
|
|
bool mContinueRunning;
|
|
|
|
public:
|
|
ThreadRunnable();
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
uint32_t
|
|
SerialNumber() const
|
|
{
|
|
return mSerialNumber;
|
|
}
|
|
|
|
nsCString GetThreadName() const
|
|
{
|
|
return nsPrintfCString("IndexedDB #%lu", mSerialNumber);
|
|
}
|
|
|
|
private:
|
|
~ThreadRunnable() override;
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
};
|
|
|
|
class ConnectionPool::TransactionInfo final
|
|
{
|
|
friend class nsAutoPtr<TransactionInfo>;
|
|
|
|
nsTHashtable<nsPtrHashKey<TransactionInfo>> mBlocking;
|
|
nsTArray<TransactionInfo*> mBlockingOrdered;
|
|
|
|
public:
|
|
DatabaseInfo* mDatabaseInfo;
|
|
const nsID mBackgroundChildLoggingId;
|
|
const nsCString mDatabaseId;
|
|
const uint64_t mTransactionId;
|
|
const int64_t mLoggingSerialNumber;
|
|
const nsTArray<nsString> mObjectStoreNames;
|
|
nsTHashtable<nsPtrHashKey<TransactionInfo>> mBlockedOn;
|
|
nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
|
|
const bool mIsWriteTransaction;
|
|
bool mRunning;
|
|
|
|
#ifdef DEBUG
|
|
bool mFinished;
|
|
#endif
|
|
|
|
TransactionInfo(DatabaseInfo* aDatabaseInfo,
|
|
const nsID& aBackgroundChildLoggingId,
|
|
const nsACString& aDatabaseId,
|
|
uint64_t aTransactionId,
|
|
int64_t aLoggingSerialNumber,
|
|
const nsTArray<nsString>& aObjectStoreNames,
|
|
bool aIsWriteTransaction,
|
|
TransactionDatabaseOperationBase* aTransactionOp);
|
|
|
|
void
|
|
AddBlockingTransaction(TransactionInfo* aTransactionInfo);
|
|
|
|
void
|
|
RemoveBlockingTransactions();
|
|
|
|
private:
|
|
~TransactionInfo();
|
|
|
|
void
|
|
MaybeUnblock(TransactionInfo* aTransactionInfo);
|
|
};
|
|
|
|
struct ConnectionPool::TransactionInfoPair final
|
|
{
|
|
friend class nsAutoPtr<TransactionInfoPair>;
|
|
|
|
// Multiple reading transactions can block future writes.
|
|
nsTArray<TransactionInfo*> mLastBlockingWrites;
|
|
// But only a single writing transaction can block future reads.
|
|
TransactionInfo* mLastBlockingReads;
|
|
|
|
TransactionInfoPair();
|
|
|
|
private:
|
|
~TransactionInfoPair();
|
|
};
|
|
|
|
/*******************************************************************************
|
|
* Actor class declarations
|
|
******************************************************************************/
|
|
|
|
class DatabaseOperationBase
|
|
: public Runnable
|
|
, public mozIStorageProgressHandler
|
|
{
|
|
friend class UpgradeFileIdsFunction;
|
|
|
|
protected:
|
|
class AutoSetProgressHandler;
|
|
|
|
typedef nsDataHashtable<nsUint64HashKey, bool> UniqueIndexTable;
|
|
|
|
nsCOMPtr<nsIEventTarget> mOwningThread;
|
|
const nsID mBackgroundChildLoggingId;
|
|
const uint64_t mLoggingSerialNumber;
|
|
nsresult mResultCode;
|
|
|
|
private:
|
|
Atomic<bool> mOperationMayProceed;
|
|
bool mActorDestroyed;
|
|
|
|
public:
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
bool
|
|
IsOnOwningThread() const
|
|
{
|
|
MOZ_ASSERT(mOwningThread);
|
|
|
|
bool current;
|
|
return NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(¤t)) && current;
|
|
}
|
|
|
|
void
|
|
AssertIsOnOwningThread() const
|
|
{
|
|
MOZ_ASSERT(IsOnBackgroundThread());
|
|
MOZ_ASSERT(IsOnOwningThread());
|
|
}
|
|
|
|
void
|
|
NoteActorDestroyed()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
mActorDestroyed = true;
|
|
mOperationMayProceed = false;
|
|
}
|
|
|
|
bool
|
|
IsActorDestroyed() const
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
return mActorDestroyed;
|
|
}
|
|
|
|
// May be called on any thread, but you should call IsActorDestroyed() if
|
|
// you know you're on the background thread because it is slightly faster.
|
|
bool
|
|
OperationMayProceed() const
|
|
{
|
|
return mOperationMayProceed;
|
|
}
|
|
|
|
const nsID&
|
|
BackgroundChildLoggingId() const
|
|
{
|
|
return mBackgroundChildLoggingId;
|
|
}
|
|
|
|
uint64_t
|
|
LoggingSerialNumber() const
|
|
{
|
|
return mLoggingSerialNumber;
|
|
}
|
|
|
|
nsresult
|
|
ResultCode() const
|
|
{
|
|
return mResultCode;
|
|
}
|
|
|
|
void
|
|
SetFailureCode(nsresult aErrorCode)
|
|
{
|
|
MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
|
|
MOZ_ASSERT(NS_FAILED(aErrorCode));
|
|
|
|
mResultCode = aErrorCode;
|
|
}
|
|
|
|
protected:
|
|
DatabaseOperationBase(const nsID& aBackgroundChildLoggingId,
|
|
uint64_t aLoggingSerialNumber)
|
|
: mOwningThread(NS_GetCurrentThread())
|
|
, mBackgroundChildLoggingId(aBackgroundChildLoggingId)
|
|
, mLoggingSerialNumber(aLoggingSerialNumber)
|
|
, mResultCode(NS_OK)
|
|
, mOperationMayProceed(true)
|
|
, mActorDestroyed(false)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
}
|
|
|
|
~DatabaseOperationBase() override
|
|
{
|
|
MOZ_ASSERT(mActorDestroyed);
|
|
}
|
|
|
|
static void
|
|
GetBindingClauseForKeyRange(const SerializedKeyRange& aKeyRange,
|
|
const nsACString& aKeyColumnName,
|
|
nsAutoCString& aBindingClause);
|
|
|
|
static uint64_t
|
|
ReinterpretDoubleAsUInt64(double aDouble);
|
|
|
|
static nsresult
|
|
GetStructuredCloneReadInfoFromStatement(mozIStorageStatement* aStatement,
|
|
uint32_t aDataIndex,
|
|
uint32_t aFileIdsIndex,
|
|
FileManager* aFileManager,
|
|
StructuredCloneReadInfo* aInfo)
|
|
{
|
|
return GetStructuredCloneReadInfoFromSource(aStatement,
|
|
aDataIndex,
|
|
aFileIdsIndex,
|
|
aFileManager,
|
|
aInfo);
|
|
}
|
|
|
|
static nsresult
|
|
GetStructuredCloneReadInfoFromValueArray(mozIStorageValueArray* aValues,
|
|
uint32_t aDataIndex,
|
|
uint32_t aFileIdsIndex,
|
|
FileManager* aFileManager,
|
|
StructuredCloneReadInfo* aInfo)
|
|
{
|
|
return GetStructuredCloneReadInfoFromSource(aValues,
|
|
aDataIndex,
|
|
aFileIdsIndex,
|
|
aFileManager,
|
|
aInfo);
|
|
}
|
|
|
|
static nsresult
|
|
BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
|
|
mozIStorageStatement* aStatement);
|
|
|
|
static nsresult
|
|
BindKeyRangeToStatement(const SerializedKeyRange& aKeyRange,
|
|
mozIStorageStatement* aStatement,
|
|
const nsCString& aLocale);
|
|
|
|
static void
|
|
AppendConditionClause(const nsACString& aColumnName,
|
|
const nsACString& aArgName,
|
|
bool aLessThan,
|
|
bool aEquals,
|
|
nsAutoCString& aResult);
|
|
|
|
static nsresult
|
|
GetUniqueIndexTableForObjectStore(
|
|
TransactionBase* aTransaction,
|
|
int64_t aObjectStoreId,
|
|
Maybe<UniqueIndexTable>& aMaybeUniqueIndexTable);
|
|
|
|
static nsresult
|
|
IndexDataValuesFromUpdateInfos(const nsTArray<IndexUpdateInfo>& aUpdateInfos,
|
|
const UniqueIndexTable& aUniqueIndexTable,
|
|
nsTArray<IndexDataValue>& aIndexValues);
|
|
|
|
static nsresult
|
|
InsertIndexTableRows(DatabaseConnection* aConnection,
|
|
const int64_t aObjectStoreId,
|
|
const Key& aObjectStoreKey,
|
|
const FallibleTArray<IndexDataValue>& aIndexValues);
|
|
|
|
static nsresult
|
|
DeleteIndexDataTableRows(DatabaseConnection* aConnection,
|
|
const Key& aObjectStoreKey,
|
|
const FallibleTArray<IndexDataValue>& aIndexValues);
|
|
|
|
static nsresult
|
|
DeleteObjectStoreDataTableRowsWithIndexes(DatabaseConnection* aConnection,
|
|
const int64_t aObjectStoreId,
|
|
const OptionalKeyRange& aKeyRange);
|
|
|
|
static nsresult
|
|
UpdateIndexValues(DatabaseConnection* aConnection,
|
|
const int64_t aObjectStoreId,
|
|
const Key& aObjectStoreKey,
|
|
const FallibleTArray<IndexDataValue>& aIndexValues);
|
|
|
|
static nsresult
|
|
ObjectStoreHasIndexes(DatabaseConnection* aConnection,
|
|
const int64_t aObjectStoreId,
|
|
bool* aHasIndexes);
|
|
|
|
private:
|
|
template <typename T>
|
|
static nsresult
|
|
GetStructuredCloneReadInfoFromSource(T* aSource,
|
|
uint32_t aDataIndex,
|
|
uint32_t aFileIdsIndex,
|
|
FileManager* aFileManager,
|
|
StructuredCloneReadInfo* aInfo);
|
|
|
|
static nsresult
|
|
GetStructuredCloneReadInfoFromBlob(const uint8_t* aBlobData,
|
|
uint32_t aBlobDataLength,
|
|
FileManager* aFileManager,
|
|
const nsAString& aFileIds,
|
|
StructuredCloneReadInfo* aInfo);
|
|
|
|
static nsresult
|
|
GetStructuredCloneReadInfoFromExternalBlob(uint64_t aIntData,
|
|
FileManager* aFileManager,
|
|
const nsAString& aFileIds,
|
|
StructuredCloneReadInfo* aInfo);
|
|
|
|
// Not to be overridden by subclasses.
|
|
NS_DECL_MOZISTORAGEPROGRESSHANDLER
|
|
};
|
|
|
|
class MOZ_STACK_CLASS DatabaseOperationBase::AutoSetProgressHandler final
|
|
{
|
|
mozIStorageConnection* mConnection;
|
|
#ifdef DEBUG
|
|
DatabaseOperationBase* mDEBUGDatabaseOp;
|
|
#endif
|
|
|
|
public:
|
|
AutoSetProgressHandler();
|
|
|
|
~AutoSetProgressHandler();
|
|
|
|
nsresult
|
|
Register(mozIStorageConnection* aConnection,
|
|
DatabaseOperationBase* aDatabaseOp);
|
|
};
|
|
|
|
class TransactionDatabaseOperationBase
|
|
: public DatabaseOperationBase
|
|
{
|
|
enum class InternalState
|
|
{
|
|
Initial,
|
|
DatabaseWork,
|
|
SendingPreprocess,
|
|
WaitingForContinue,
|
|
SendingResults,
|
|
Completed
|
|
};
|
|
|
|
RefPtr<TransactionBase> mTransaction;
|
|
const int64_t mTransactionLoggingSerialNumber;
|
|
InternalState mInternalState;
|
|
const bool mTransactionIsAborted;
|
|
|
|
public:
|
|
void
|
|
AssertIsOnConnectionThread() const
|
|
#ifdef DEBUG
|
|
;
|
|
#else
|
|
{ }
|
|
#endif
|
|
|
|
uint64_t
|
|
StartOnConnectionPool(const nsID& aBackgroundChildLoggingId,
|
|
const nsACString& aDatabaseId,
|
|
int64_t aLoggingSerialNumber,
|
|
const nsTArray<nsString>& aObjectStoreNames,
|
|
bool aIsWriteTransaction);
|
|
|
|
void
|
|
DispatchToConnectionPool();
|
|
|
|
TransactionBase*
|
|
Transaction() const
|
|
{
|
|
MOZ_ASSERT(mTransaction);
|
|
|
|
return mTransaction;
|
|
}
|
|
|
|
void
|
|
NoteContinueReceived();
|
|
|
|
// May be overridden by subclasses if they need to perform work on the
|
|
// background thread before being dispatched. Returning false will kill the
|
|
// child actors and prevent dispatch.
|
|
virtual bool
|
|
Init(TransactionBase* aTransaction);
|
|
|
|
// This callback will be called on the background thread before releasing the
|
|
// final reference to this request object. Subclasses may perform any
|
|
// additional cleanup here but must always call the base class implementation.
|
|
virtual void
|
|
Cleanup();
|
|
|
|
protected:
|
|
explicit
|
|
TransactionDatabaseOperationBase(TransactionBase* aTransaction);
|
|
|
|
TransactionDatabaseOperationBase(TransactionBase* aTransaction,
|
|
uint64_t aLoggingSerialNumber);
|
|
|
|
~TransactionDatabaseOperationBase() override;
|
|
|
|
virtual void
|
|
RunOnConnectionThread();
|
|
|
|
// Must be overridden in subclasses. Called on the target thread to allow the
|
|
// subclass to perform necessary database or file operations. A successful
|
|
// return value will trigger a SendSuccessResult callback on the background
|
|
// thread while a failure value will trigger a SendFailureResult callback.
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) = 0;
|
|
|
|
// May be overriden in subclasses. Called on the background thread to decide
|
|
// if the subclass needs to send any preprocess info to the child actor.
|
|
virtual bool
|
|
HasPreprocessInfo();
|
|
|
|
// May be overriden in subclasses. Called on the background thread to allow
|
|
// the subclass to serialize its preprocess info and send it to the child
|
|
// actor. A successful return value will trigger a wait for a
|
|
// NoteContinueReceived callback on the background thread while a failure
|
|
// value will trigger a SendFailureResult callback.
|
|
virtual nsresult
|
|
SendPreprocessInfo();
|
|
|
|
// Must be overridden in subclasses. Called on the background thread to allow
|
|
// the subclass to serialize its results and send them to the child actor. A
|
|
// failed return value will trigger a SendFailureResult callback.
|
|
virtual nsresult
|
|
SendSuccessResult() = 0;
|
|
|
|
// Must be overridden in subclasses. Called on the background thread to allow
|
|
// the subclass to send its failure code. Returning false will cause the
|
|
// transaction to be aborted with aResultCode. Returning true will not cause
|
|
// the transaction to be aborted.
|
|
virtual bool
|
|
SendFailureResult(nsresult aResultCode) = 0;
|
|
|
|
private:
|
|
void
|
|
SendToConnectionPool();
|
|
|
|
void
|
|
SendPreprocess();
|
|
|
|
void
|
|
SendResults();
|
|
|
|
void
|
|
SendPreprocessInfoOrResults(bool aSendPreprocessInfo);
|
|
|
|
// Not to be overridden by subclasses.
|
|
NS_DECL_NSIRUNNABLE
|
|
};
|
|
|
|
class Factory final
|
|
: public PBackgroundIDBFactoryParent
|
|
{
|
|
|
|
RefPtr<DatabaseLoggingInfo> mLoggingInfo;
|
|
|
|
#ifdef DEBUG
|
|
bool mActorDestroyed;
|
|
#endif
|
|
|
|
public:
|
|
static already_AddRefed<Factory>
|
|
Create(const LoggingInfo& aLoggingInfo);
|
|
|
|
DatabaseLoggingInfo*
|
|
GetLoggingInfo() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mLoggingInfo);
|
|
|
|
return mLoggingInfo;
|
|
}
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Factory)
|
|
|
|
private:
|
|
// Only constructed in Create().
|
|
explicit
|
|
Factory(already_AddRefed<DatabaseLoggingInfo> aLoggingInfo);
|
|
|
|
// Reference counted.
|
|
~Factory() override;
|
|
|
|
// IPDL methods are only called by IPDL.
|
|
void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvDeleteMe() override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvIncrementLoggingRequestSerialNumber() override;
|
|
|
|
PBackgroundIDBFactoryRequestParent*
|
|
AllocPBackgroundIDBFactoryRequestParent(const FactoryRequestParams& aParams)
|
|
override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvPBackgroundIDBFactoryRequestConstructor(
|
|
PBackgroundIDBFactoryRequestParent* aActor,
|
|
const FactoryRequestParams& aParams)
|
|
override;
|
|
|
|
bool
|
|
DeallocPBackgroundIDBFactoryRequestParent(
|
|
PBackgroundIDBFactoryRequestParent* aActor)
|
|
override;
|
|
|
|
PBackgroundIDBDatabaseParent*
|
|
AllocPBackgroundIDBDatabaseParent(
|
|
const DatabaseSpec& aSpec,
|
|
PBackgroundIDBFactoryRequestParent* aRequest)
|
|
override;
|
|
|
|
bool
|
|
DeallocPBackgroundIDBDatabaseParent(PBackgroundIDBDatabaseParent* aActor)
|
|
override;
|
|
};
|
|
|
|
class WaitForTransactionsHelper final
|
|
: public Runnable
|
|
{
|
|
const nsCString mDatabaseId;
|
|
nsCOMPtr<nsIRunnable> mCallback;
|
|
|
|
enum class State
|
|
{
|
|
Initial = 0,
|
|
WaitingForTransactions,
|
|
WaitingForFileHandles,
|
|
Complete
|
|
} mState;
|
|
|
|
public:
|
|
WaitForTransactionsHelper(const nsCString& aDatabaseId,
|
|
nsIRunnable* aCallback)
|
|
: mDatabaseId(aDatabaseId)
|
|
, mCallback(aCallback)
|
|
, mState(State::Initial)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!aDatabaseId.IsEmpty());
|
|
MOZ_ASSERT(aCallback);
|
|
}
|
|
|
|
void
|
|
WaitForTransactions();
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
private:
|
|
~WaitForTransactionsHelper() override
|
|
{
|
|
MOZ_ASSERT(!mCallback);
|
|
MOZ_ASSERT(mState == State::Complete);
|
|
}
|
|
|
|
void
|
|
MaybeWaitForTransactions();
|
|
|
|
void
|
|
MaybeWaitForFileHandles();
|
|
|
|
void
|
|
CallCallback();
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
};
|
|
|
|
class Database final
|
|
: public PBackgroundIDBDatabaseParent
|
|
{
|
|
friend class VersionChangeTransaction;
|
|
|
|
class StartTransactionOp;
|
|
|
|
private:
|
|
RefPtr<Factory> mFactory;
|
|
RefPtr<FullDatabaseMetadata> mMetadata;
|
|
RefPtr<FileManager> mFileManager;
|
|
RefPtr<DirectoryLock> mDirectoryLock;
|
|
nsTHashtable<nsPtrHashKey<TransactionBase>> mTransactions;
|
|
nsTHashtable<nsPtrHashKey<MutableFile>> mMutableFiles;
|
|
RefPtr<DatabaseConnection> mConnection;
|
|
const PrincipalInfo mPrincipalInfo;
|
|
const Maybe<ContentParentId> mOptionalContentParentId;
|
|
const nsCString mGroup;
|
|
const nsCString mOrigin;
|
|
const nsCString mId;
|
|
const nsString mFilePath;
|
|
uint32_t mActiveMutableFileCount;
|
|
const uint32_t mTelemetryId;
|
|
const PersistenceType mPersistenceType;
|
|
const bool mFileHandleDisabled;
|
|
const bool mChromeWriteAccessAllowed;
|
|
bool mClosed;
|
|
bool mInvalidated;
|
|
bool mActorWasAlive;
|
|
bool mActorDestroyed;
|
|
bool mMetadataCleanedUp;
|
|
|
|
public:
|
|
// Created by OpenDatabaseOp.
|
|
Database(Factory* aFactory,
|
|
const PrincipalInfo& aPrincipalInfo,
|
|
const Maybe<ContentParentId>& aOptionalContentParentId,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
uint32_t aTelemetryId,
|
|
FullDatabaseMetadata* aMetadata,
|
|
FileManager* aFileManager,
|
|
already_AddRefed<DirectoryLock> aDirectoryLock,
|
|
bool aFileHandleDisabled,
|
|
bool aChromeWriteAccessAllowed);
|
|
|
|
void
|
|
AssertIsOnConnectionThread() const
|
|
{
|
|
#ifdef DEBUG
|
|
if (mConnection) {
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
} else {
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(mInvalidated);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Database)
|
|
|
|
void
|
|
Invalidate();
|
|
|
|
const PrincipalInfo&
|
|
GetPrincipalInfo() const
|
|
{
|
|
return mPrincipalInfo;
|
|
}
|
|
|
|
bool
|
|
IsOwnedByProcess(ContentParentId aContentParentId) const
|
|
{
|
|
return
|
|
mOptionalContentParentId &&
|
|
mOptionalContentParentId.value() == aContentParentId;
|
|
}
|
|
|
|
const nsCString&
|
|
Group() const
|
|
{
|
|
return mGroup;
|
|
}
|
|
|
|
const nsCString&
|
|
Origin() const
|
|
{
|
|
return mOrigin;
|
|
}
|
|
|
|
const nsCString&
|
|
Id() const
|
|
{
|
|
return mId;
|
|
}
|
|
|
|
uint32_t
|
|
TelemetryId() const
|
|
{
|
|
return mTelemetryId;
|
|
}
|
|
|
|
PersistenceType
|
|
Type() const
|
|
{
|
|
return mPersistenceType;
|
|
}
|
|
|
|
const nsString&
|
|
FilePath() const
|
|
{
|
|
return mFilePath;
|
|
}
|
|
|
|
FileManager*
|
|
GetFileManager() const
|
|
{
|
|
return mFileManager;
|
|
}
|
|
|
|
FullDatabaseMetadata*
|
|
Metadata() const
|
|
{
|
|
MOZ_ASSERT(mMetadata);
|
|
return mMetadata;
|
|
}
|
|
|
|
PBackgroundParent*
|
|
GetBackgroundParent() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
return Manager()->Manager();
|
|
}
|
|
|
|
DatabaseLoggingInfo*
|
|
GetLoggingInfo() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mFactory);
|
|
|
|
return mFactory->GetLoggingInfo();
|
|
}
|
|
|
|
void
|
|
ReleaseTransactionThreadObjects();
|
|
|
|
void
|
|
ReleaseBackgroundThreadObjects();
|
|
|
|
bool
|
|
RegisterTransaction(TransactionBase* aTransaction);
|
|
|
|
void
|
|
UnregisterTransaction(TransactionBase* aTransaction);
|
|
|
|
bool
|
|
IsFileHandleDisabled() const
|
|
{
|
|
return mFileHandleDisabled;
|
|
}
|
|
|
|
bool
|
|
RegisterMutableFile(MutableFile* aMutableFile);
|
|
|
|
void
|
|
UnregisterMutableFile(MutableFile* aMutableFile);
|
|
|
|
void
|
|
NoteActiveMutableFile();
|
|
|
|
void
|
|
NoteInactiveMutableFile();
|
|
|
|
void
|
|
SetActorAlive();
|
|
|
|
bool
|
|
IsActorAlive() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return mActorWasAlive && !mActorDestroyed;
|
|
}
|
|
|
|
bool
|
|
IsActorDestroyed() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return mActorWasAlive && mActorDestroyed;
|
|
}
|
|
|
|
bool
|
|
IsClosed() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return mClosed;
|
|
}
|
|
|
|
bool
|
|
IsInvalidated() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return mInvalidated;
|
|
}
|
|
|
|
nsresult
|
|
EnsureConnection();
|
|
|
|
DatabaseConnection*
|
|
GetConnection() const
|
|
{
|
|
#ifdef DEBUG
|
|
if (mConnection) {
|
|
mConnection->AssertIsOnConnectionThread();
|
|
}
|
|
#endif
|
|
|
|
return mConnection;
|
|
}
|
|
|
|
private:
|
|
// Reference counted.
|
|
~Database() override
|
|
{
|
|
MOZ_ASSERT(mClosed);
|
|
MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
|
|
}
|
|
|
|
bool
|
|
CloseInternal();
|
|
|
|
void
|
|
MaybeCloseConnection();
|
|
|
|
void
|
|
ConnectionClosedCallback();
|
|
|
|
void
|
|
CleanupMetadata();
|
|
|
|
bool
|
|
VerifyRequestParams(const DatabaseRequestParams& aParams) const;
|
|
|
|
// IPDL methods are only called by IPDL.
|
|
void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
PBackgroundIDBDatabaseFileParent*
|
|
AllocPBackgroundIDBDatabaseFileParent(PBlobParent* aBlobParent)
|
|
override;
|
|
|
|
bool
|
|
DeallocPBackgroundIDBDatabaseFileParent(
|
|
PBackgroundIDBDatabaseFileParent* aActor)
|
|
override;
|
|
|
|
PBackgroundIDBDatabaseRequestParent*
|
|
AllocPBackgroundIDBDatabaseRequestParent(const DatabaseRequestParams& aParams)
|
|
override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvPBackgroundIDBDatabaseRequestConstructor(
|
|
PBackgroundIDBDatabaseRequestParent* aActor,
|
|
const DatabaseRequestParams& aParams)
|
|
override;
|
|
|
|
bool
|
|
DeallocPBackgroundIDBDatabaseRequestParent(
|
|
PBackgroundIDBDatabaseRequestParent* aActor)
|
|
override;
|
|
|
|
PBackgroundIDBTransactionParent*
|
|
AllocPBackgroundIDBTransactionParent(
|
|
const nsTArray<nsString>& aObjectStoreNames,
|
|
const Mode& aMode)
|
|
override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvPBackgroundIDBTransactionConstructor(
|
|
PBackgroundIDBTransactionParent* aActor,
|
|
InfallibleTArray<nsString>&& aObjectStoreNames,
|
|
const Mode& aMode)
|
|
override;
|
|
|
|
bool
|
|
DeallocPBackgroundIDBTransactionParent(
|
|
PBackgroundIDBTransactionParent* aActor)
|
|
override;
|
|
|
|
PBackgroundIDBVersionChangeTransactionParent*
|
|
AllocPBackgroundIDBVersionChangeTransactionParent(
|
|
const uint64_t& aCurrentVersion,
|
|
const uint64_t& aRequestedVersion,
|
|
const int64_t& aNextObjectStoreId,
|
|
const int64_t& aNextIndexId)
|
|
override;
|
|
|
|
bool
|
|
DeallocPBackgroundIDBVersionChangeTransactionParent(
|
|
PBackgroundIDBVersionChangeTransactionParent* aActor)
|
|
override;
|
|
|
|
PBackgroundMutableFileParent*
|
|
AllocPBackgroundMutableFileParent(const nsString& aName,
|
|
const nsString& aType) override;
|
|
|
|
bool
|
|
DeallocPBackgroundMutableFileParent(PBackgroundMutableFileParent* aActor)
|
|
override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvDeleteMe() override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvBlocked() override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvClose() override;
|
|
};
|
|
|
|
class Database::StartTransactionOp final
|
|
: public TransactionDatabaseOperationBase
|
|
{
|
|
friend class Database;
|
|
|
|
private:
|
|
explicit
|
|
StartTransactionOp(TransactionBase* aTransaction)
|
|
: TransactionDatabaseOperationBase(aTransaction,
|
|
/* aLoggingSerialNumber */ 0)
|
|
{ }
|
|
|
|
~StartTransactionOp() override = default;
|
|
|
|
void
|
|
RunOnConnectionThread() override;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
nsresult
|
|
SendSuccessResult() override;
|
|
|
|
bool
|
|
SendFailureResult(nsresult aResultCode) override;
|
|
|
|
void
|
|
Cleanup() override;
|
|
};
|
|
|
|
/**
|
|
* In coordination with IDBDatabase's mFileActors weak-map on the child side, a
|
|
* long-lived mapping from a child process's live Blobs to their corresponding
|
|
* FileInfo in our owning database. Assists in avoiding redundant IPC traffic
|
|
* and disk storage. This includes both:
|
|
* - Blobs retrieved from this database and sent to the child that do not need
|
|
* to be written to disk because they already exist on disk in this database's
|
|
* files directory.
|
|
* - Blobs retrieved from other databases (that are therefore !IsShareable())
|
|
* or from anywhere else that will need to be written to this database's files
|
|
* directory. In this case we will hold a reference to its BlobImpl in
|
|
* mBlobImpl until we have successfully written the Blob to disk.
|
|
*
|
|
* Relevant Blob context: Blobs sent from the parent process to child processes
|
|
* are automatically linked back to their source BlobImpl when the child process
|
|
* references the Blob via IPC. (This is true even when a new "KnownBlob" actor
|
|
* must be created because the reference is occurring on a different thread than
|
|
* the PBlob actor created when the blob was sent to the child.) However, when
|
|
* getting an actor in the child process for sending an in-child-created Blob to
|
|
* the parent process, there is (currently) no Blob machinery to automatically
|
|
* establish and reuse a long-lived Actor. As a result, without IDB's weak-map
|
|
* cleverness, a memory-backed Blob repeatedly sent from the child to the parent
|
|
* would appear as a different Blob each time, requiring the Blob data to be
|
|
* sent over IPC each time as well as potentially needing to be written to disk
|
|
* each time.
|
|
*
|
|
* This object remains alive as long as there is an active child actor or an
|
|
* ObjectStoreAddOrPutRequestOp::StoredFileInfo for a queued or active add/put
|
|
* op is holding a reference to us.
|
|
*/
|
|
class DatabaseFile final
|
|
: public PBackgroundIDBDatabaseFileParent
|
|
{
|
|
friend class Database;
|
|
|
|
// mBlobImpl's ownership lifecycle:
|
|
// - Initialized on the background thread at creation time. Then
|
|
// responsibility is handed off to the connection thread.
|
|
// - Checked and used by the connection thread to generate a stream to write
|
|
// the blob to disk by an add/put operation.
|
|
// - Cleared on the connection thread once the file has successfully been
|
|
// written to disk.
|
|
RefPtr<BlobImpl> mBlobImpl;
|
|
RefPtr<FileInfo> mFileInfo;
|
|
|
|
public:
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::DatabaseFile);
|
|
|
|
FileInfo*
|
|
GetFileInfo() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return mFileInfo;
|
|
}
|
|
|
|
/**
|
|
* If mBlobImpl is non-null (implying the contents of this file have not yet
|
|
* been written to disk), then return an input stream that is guaranteed to
|
|
* block on reads and never return NS_BASE_STREAM_WOULD_BLOCK. If mBlobImpl
|
|
* is null (because the contents have been written to disk), returns null.
|
|
*
|
|
* Because this method does I/O, it should only be called on a database I/O
|
|
* thread, not on PBackground. Note that we actually open the stream on this
|
|
* thread, where previously it was opened on PBackground. This is safe and
|
|
* equally efficient because blob implementations are thread-safe and blobs in
|
|
* the parent are fully populated. (This would not be efficient in the child
|
|
* where the open request would have to be dispatched back to the PBackground
|
|
* thread because of the need to potentially interact with actors.)
|
|
*
|
|
* We enforce this guarantee by wrapping the stream in a blocking pipe unless
|
|
* either is true:
|
|
* - The stream is already a blocking stream. (AKA it's not non-blocking.)
|
|
* This is the case for nsFileStreamBase-derived implementations and
|
|
* appropriately configured nsPipes (but not PSendStream-generated pipes).
|
|
* - The stream already contains the entire contents of the Blob. For
|
|
* example, nsStringInputStreams report as non-blocking, but also have their
|
|
* entire contents synchronously available, so they will never return
|
|
* NS_BASE_STREAM_WOULD_BLOCK. There is no need to wrap these in a pipe.
|
|
* (It's also very common for SendStream-based Blobs to have their contents
|
|
* entirely streamed into the parent process by the time this call is
|
|
* issued.)
|
|
*
|
|
* This additional logic is necessary because our database operations all
|
|
* are written in such a way that the operation is assumed to have completed
|
|
* when they yield control-flow, and:
|
|
* - When memory-backed blobs cross a certain threshold (1MiB at the time of
|
|
* writing), they will be sent up from the child via PSendStream in chunks
|
|
* to a non-blocking pipe that will return NS_BASE_STREAM_WOULD_BLOCK.
|
|
* - Other Blob types could potentially be non-blocking. (We're not making
|
|
* any assumptions.)
|
|
*/
|
|
already_AddRefed<nsIInputStream>
|
|
GetBlockingInputStream(ErrorResult &rv) const;
|
|
|
|
/**
|
|
* To be called upon successful copying of the stream GetBlockingInputStream()
|
|
* returned so that we won't try and redundantly write the file to disk in the
|
|
* future. This is a separate step from GetBlockingInputStream() because
|
|
* the write could fail due to quota errors that happen now but that might
|
|
* not happen in a future attempt.
|
|
*/
|
|
void
|
|
WriteSucceededClearBlobImpl()
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
mBlobImpl = nullptr;
|
|
}
|
|
|
|
private:
|
|
// Called when sending to the child.
|
|
explicit DatabaseFile(FileInfo* aFileInfo)
|
|
: mFileInfo(aFileInfo)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aFileInfo);
|
|
}
|
|
|
|
// Called when receiving from the child.
|
|
DatabaseFile(BlobImpl* aBlobImpl, FileInfo* aFileInfo)
|
|
: mBlobImpl(aBlobImpl)
|
|
, mFileInfo(aFileInfo)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aBlobImpl);
|
|
MOZ_ASSERT(aFileInfo);
|
|
}
|
|
|
|
~DatabaseFile() override = default;
|
|
|
|
void
|
|
ActorDestroy(ActorDestroyReason aWhy) override
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
}
|
|
};
|
|
|
|
already_AddRefed<nsIInputStream>
|
|
DatabaseFile::GetBlockingInputStream(ErrorResult &rv) const
|
|
{
|
|
// We should only be called from our DB connection thread, not the background
|
|
// thread.
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
if (!mBlobImpl) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIInputStream> inputStream;
|
|
mBlobImpl->GetInternalStream(getter_AddRefs(inputStream), rv);
|
|
if (rv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// If it's non-blocking we may need a pipe.
|
|
bool pipeNeeded;
|
|
rv = inputStream->IsNonBlocking(&pipeNeeded);
|
|
if (rv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
// We don't need a pipe if all the bytes might already be available.
|
|
if (pipeNeeded) {
|
|
uint64_t available;
|
|
rv = inputStream->Available(&available);
|
|
if (rv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
uint64_t blobSize = mBlobImpl->GetSize(rv);
|
|
if (rv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (available == blobSize) {
|
|
pipeNeeded = false;
|
|
}
|
|
}
|
|
|
|
if (pipeNeeded) {
|
|
nsCOMPtr<nsIEventTarget> target =
|
|
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
|
if (!target) {
|
|
rv.Throw(NS_ERROR_UNEXPECTED);
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIInputStream> pipeInputStream;
|
|
nsCOMPtr<nsIOutputStream> pipeOutputStream;
|
|
|
|
rv = NS_NewPipe(
|
|
getter_AddRefs(pipeInputStream),
|
|
getter_AddRefs(pipeOutputStream),
|
|
0, 0, // default buffering is fine;
|
|
false, // we absolutely want a blocking input stream
|
|
true); // we don't need the writer to block
|
|
if (rv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
rv = NS_AsyncCopy(inputStream, pipeOutputStream, target);
|
|
if (rv.Failed()) {
|
|
return nullptr;
|
|
}
|
|
|
|
inputStream = pipeInputStream;
|
|
}
|
|
|
|
return inputStream.forget();
|
|
}
|
|
|
|
|
|
class TransactionBase
|
|
{
|
|
friend class Cursor;
|
|
|
|
class CommitOp;
|
|
|
|
protected:
|
|
typedef IDBTransaction::Mode Mode;
|
|
|
|
private:
|
|
RefPtr<Database> mDatabase;
|
|
nsTArray<RefPtr<FullObjectStoreMetadata>>
|
|
mModifiedAutoIncrementObjectStoreMetadataArray;
|
|
uint64_t mTransactionId;
|
|
const nsCString mDatabaseId;
|
|
const int64_t mLoggingSerialNumber;
|
|
uint64_t mActiveRequestCount;
|
|
Atomic<bool> mInvalidatedOnAnyThread;
|
|
const Mode mMode;
|
|
bool mHasBeenActive;
|
|
bool mHasBeenActiveOnConnectionThread;
|
|
bool mActorDestroyed;
|
|
bool mInvalidated;
|
|
|
|
protected:
|
|
nsresult mResultCode;
|
|
bool mCommitOrAbortReceived;
|
|
bool mCommittedOrAborted;
|
|
bool mForceAborted;
|
|
|
|
public:
|
|
void
|
|
AssertIsOnConnectionThread() const
|
|
{
|
|
MOZ_ASSERT(mDatabase);
|
|
mDatabase->AssertIsOnConnectionThread();
|
|
}
|
|
|
|
bool
|
|
IsActorDestroyed() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return mActorDestroyed;
|
|
}
|
|
|
|
// Must be called on the background thread.
|
|
bool
|
|
IsInvalidated() const
|
|
{
|
|
MOZ_ASSERT(IsOnBackgroundThread(), "Use IsInvalidatedOnAnyThread()");
|
|
MOZ_ASSERT_IF(mInvalidated, NS_FAILED(mResultCode));
|
|
|
|
return mInvalidated;
|
|
}
|
|
|
|
// May be called on any thread, but is more expensive than IsInvalidated().
|
|
bool
|
|
IsInvalidatedOnAnyThread() const
|
|
{
|
|
return mInvalidatedOnAnyThread;
|
|
}
|
|
|
|
void
|
|
SetActive(uint64_t aTransactionId)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aTransactionId);
|
|
|
|
mTransactionId = aTransactionId;
|
|
mHasBeenActive = true;
|
|
}
|
|
|
|
void
|
|
SetActiveOnConnectionThread()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
mHasBeenActiveOnConnectionThread = true;
|
|
}
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(
|
|
mozilla::dom::indexedDB::TransactionBase)
|
|
|
|
void
|
|
Abort(nsresult aResultCode, bool aForce);
|
|
|
|
uint64_t
|
|
TransactionId() const
|
|
{
|
|
return mTransactionId;
|
|
}
|
|
|
|
const nsCString&
|
|
DatabaseId() const
|
|
{
|
|
return mDatabaseId;
|
|
}
|
|
|
|
Mode
|
|
GetMode() const
|
|
{
|
|
return mMode;
|
|
}
|
|
|
|
Database*
|
|
GetDatabase() const
|
|
{
|
|
MOZ_ASSERT(mDatabase);
|
|
|
|
return mDatabase;
|
|
}
|
|
|
|
DatabaseLoggingInfo*
|
|
GetLoggingInfo() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mDatabase);
|
|
|
|
return mDatabase->GetLoggingInfo();
|
|
}
|
|
|
|
int64_t
|
|
LoggingSerialNumber() const
|
|
{
|
|
return mLoggingSerialNumber;
|
|
}
|
|
|
|
bool
|
|
IsAborted() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return NS_FAILED(mResultCode);
|
|
}
|
|
|
|
already_AddRefed<FullObjectStoreMetadata>
|
|
GetMetadataForObjectStoreId(int64_t aObjectStoreId) const;
|
|
|
|
already_AddRefed<FullIndexMetadata>
|
|
GetMetadataForIndexId(FullObjectStoreMetadata* const aObjectStoreMetadata,
|
|
int64_t aIndexId) const;
|
|
|
|
PBackgroundParent*
|
|
GetBackgroundParent() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
return GetDatabase()->GetBackgroundParent();
|
|
}
|
|
|
|
void
|
|
NoteModifiedAutoIncrementObjectStore(FullObjectStoreMetadata* aMetadata);
|
|
|
|
void
|
|
ForgetModifiedAutoIncrementObjectStore(FullObjectStoreMetadata* aMetadata);
|
|
|
|
void
|
|
NoteActiveRequest();
|
|
|
|
void
|
|
NoteFinishedRequest();
|
|
|
|
void
|
|
Invalidate();
|
|
|
|
protected:
|
|
TransactionBase(Database* aDatabase, Mode aMode);
|
|
|
|
virtual
|
|
~TransactionBase();
|
|
|
|
void
|
|
NoteActorDestroyed()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
mActorDestroyed = true;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
// Only called by VersionChangeTransaction.
|
|
void
|
|
FakeActorDestroyed()
|
|
{
|
|
mActorDestroyed = true;
|
|
}
|
|
#endif
|
|
|
|
bool
|
|
RecvCommit();
|
|
|
|
bool
|
|
RecvAbort(nsresult aResultCode);
|
|
|
|
void
|
|
MaybeCommitOrAbort()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
// If we've already committed or aborted then there's nothing else to do.
|
|
if (mCommittedOrAborted) {
|
|
return;
|
|
}
|
|
|
|
// If there are active requests then we have to wait for those requests to
|
|
// complete (see NoteFinishedRequest).
|
|
if (mActiveRequestCount) {
|
|
return;
|
|
}
|
|
|
|
// If we haven't yet received a commit or abort message then there could be
|
|
// additional requests coming so we should wait unless we're being forced to
|
|
// abort.
|
|
if (!mCommitOrAbortReceived && !mForceAborted) {
|
|
return;
|
|
}
|
|
|
|
CommitOrAbort();
|
|
}
|
|
|
|
PBackgroundIDBRequestParent*
|
|
AllocRequest(const RequestParams& aParams, bool aTrustParams);
|
|
|
|
bool
|
|
StartRequest(PBackgroundIDBRequestParent* aActor);
|
|
|
|
bool
|
|
DeallocRequest(PBackgroundIDBRequestParent* aActor);
|
|
|
|
PBackgroundIDBCursorParent*
|
|
AllocCursor(const OpenCursorParams& aParams, bool aTrustParams);
|
|
|
|
bool
|
|
StartCursor(PBackgroundIDBCursorParent* aActor,
|
|
const OpenCursorParams& aParams);
|
|
|
|
bool
|
|
DeallocCursor(PBackgroundIDBCursorParent* aActor);
|
|
|
|
virtual void
|
|
UpdateMetadata(nsresult aResult)
|
|
{ }
|
|
|
|
virtual void
|
|
SendCompleteNotification(nsresult aResult) = 0;
|
|
|
|
private:
|
|
bool
|
|
VerifyRequestParams(const RequestParams& aParams) const;
|
|
|
|
bool
|
|
VerifyRequestParams(const SerializedKeyRange& aKeyRange) const;
|
|
|
|
bool
|
|
VerifyRequestParams(const ObjectStoreAddPutParams& aParams) const;
|
|
|
|
bool
|
|
VerifyRequestParams(const OptionalKeyRange& aKeyRange) const;
|
|
|
|
void
|
|
CommitOrAbort();
|
|
};
|
|
|
|
class TransactionBase::CommitOp final
|
|
: public DatabaseOperationBase
|
|
, public ConnectionPool::FinishCallback
|
|
{
|
|
friend class TransactionBase;
|
|
|
|
RefPtr<TransactionBase> mTransaction;
|
|
nsresult mResultCode;
|
|
|
|
private:
|
|
CommitOp(TransactionBase* aTransaction, nsresult aResultCode);
|
|
|
|
~CommitOp() override = default;
|
|
|
|
// Writes new autoIncrement counts to database.
|
|
nsresult
|
|
WriteAutoIncrementCounts();
|
|
|
|
// Updates counts after a database activity has finished.
|
|
void
|
|
CommitOrRollbackAutoIncrementCounts();
|
|
|
|
void
|
|
AssertForeignKeyConsistency(DatabaseConnection* aConnection)
|
|
#ifdef DEBUG
|
|
;
|
|
#else
|
|
{ }
|
|
#endif
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
|
|
void
|
|
TransactionFinishedBeforeUnblock() override;
|
|
|
|
void
|
|
TransactionFinishedAfterUnblock() override;
|
|
|
|
public:
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
};
|
|
|
|
class NormalTransaction final
|
|
: public TransactionBase
|
|
, public PBackgroundIDBTransactionParent
|
|
{
|
|
friend class Database;
|
|
|
|
nsTArray<RefPtr<FullObjectStoreMetadata>> mObjectStores;
|
|
|
|
private:
|
|
// This constructor is only called by Database.
|
|
NormalTransaction(Database* aDatabase,
|
|
TransactionBase::Mode aMode,
|
|
nsTArray<RefPtr<FullObjectStoreMetadata>>& aObjectStores);
|
|
|
|
// Reference counted.
|
|
~NormalTransaction() override = default;
|
|
|
|
bool
|
|
IsSameProcessActor();
|
|
|
|
// Only called by TransactionBase.
|
|
void
|
|
SendCompleteNotification(nsresult aResult) override;
|
|
|
|
// IPDL methods are only called by IPDL.
|
|
void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvDeleteMe() override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvCommit() override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvAbort(const nsresult& aResultCode) override;
|
|
|
|
PBackgroundIDBRequestParent*
|
|
AllocPBackgroundIDBRequestParent(const RequestParams& aParams) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvPBackgroundIDBRequestConstructor(PBackgroundIDBRequestParent* aActor,
|
|
const RequestParams& aParams)
|
|
override;
|
|
|
|
bool
|
|
DeallocPBackgroundIDBRequestParent(PBackgroundIDBRequestParent* aActor)
|
|
override;
|
|
|
|
PBackgroundIDBCursorParent*
|
|
AllocPBackgroundIDBCursorParent(const OpenCursorParams& aParams) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvPBackgroundIDBCursorConstructor(PBackgroundIDBCursorParent* aActor,
|
|
const OpenCursorParams& aParams)
|
|
override;
|
|
|
|
bool
|
|
DeallocPBackgroundIDBCursorParent(PBackgroundIDBCursorParent* aActor)
|
|
override;
|
|
};
|
|
|
|
class VersionChangeTransaction final
|
|
: public TransactionBase
|
|
, public PBackgroundIDBVersionChangeTransactionParent
|
|
{
|
|
friend class OpenDatabaseOp;
|
|
|
|
RefPtr<OpenDatabaseOp> mOpenDatabaseOp;
|
|
RefPtr<FullDatabaseMetadata> mOldMetadata;
|
|
|
|
bool mActorWasAlive;
|
|
|
|
private:
|
|
// Only called by OpenDatabaseOp.
|
|
explicit VersionChangeTransaction(OpenDatabaseOp* aOpenDatabaseOp);
|
|
|
|
// Reference counted.
|
|
~VersionChangeTransaction() override;
|
|
|
|
bool
|
|
IsSameProcessActor();
|
|
|
|
// Only called by OpenDatabaseOp.
|
|
bool
|
|
CopyDatabaseMetadata();
|
|
|
|
void
|
|
SetActorAlive();
|
|
|
|
// Only called by TransactionBase.
|
|
void
|
|
UpdateMetadata(nsresult aResult) override;
|
|
|
|
// Only called by TransactionBase.
|
|
void
|
|
SendCompleteNotification(nsresult aResult) override;
|
|
|
|
// IPDL methods are only called by IPDL.
|
|
void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvDeleteMe() override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvCommit() override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvAbort(const nsresult& aResultCode) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvCreateObjectStore(const ObjectStoreMetadata& aMetadata) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvDeleteObjectStore(const int64_t& aObjectStoreId) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvRenameObjectStore(const int64_t& aObjectStoreId,
|
|
const nsString& aName) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvCreateIndex(const int64_t& aObjectStoreId,
|
|
const IndexMetadata& aMetadata) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvDeleteIndex(const int64_t& aObjectStoreId,
|
|
const int64_t& aIndexId) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvRenameIndex(const int64_t& aObjectStoreId,
|
|
const int64_t& aIndexId,
|
|
const nsString& aName) override;
|
|
|
|
PBackgroundIDBRequestParent*
|
|
AllocPBackgroundIDBRequestParent(const RequestParams& aParams) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvPBackgroundIDBRequestConstructor(PBackgroundIDBRequestParent* aActor,
|
|
const RequestParams& aParams)
|
|
override;
|
|
|
|
bool
|
|
DeallocPBackgroundIDBRequestParent(PBackgroundIDBRequestParent* aActor)
|
|
override;
|
|
|
|
PBackgroundIDBCursorParent*
|
|
AllocPBackgroundIDBCursorParent(const OpenCursorParams& aParams) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvPBackgroundIDBCursorConstructor(PBackgroundIDBCursorParent* aActor,
|
|
const OpenCursorParams& aParams)
|
|
override;
|
|
|
|
bool
|
|
DeallocPBackgroundIDBCursorParent(PBackgroundIDBCursorParent* aActor)
|
|
override;
|
|
};
|
|
|
|
class MutableFile
|
|
: public BackgroundMutableFileParentBase
|
|
{
|
|
RefPtr<Database> mDatabase;
|
|
RefPtr<FileInfo> mFileInfo;
|
|
|
|
public:
|
|
static already_AddRefed<MutableFile>
|
|
Create(nsIFile* aFile,
|
|
Database* aDatabase,
|
|
FileInfo* aFileInfo);
|
|
|
|
Database*
|
|
GetDatabase() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mDatabase);
|
|
|
|
return mDatabase;
|
|
}
|
|
|
|
FileInfo*
|
|
GetFileInfo() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mFileInfo);
|
|
|
|
return mFileInfo;
|
|
}
|
|
|
|
void
|
|
NoteActiveState() override;
|
|
|
|
void
|
|
NoteInactiveState() override;
|
|
|
|
PBackgroundParent*
|
|
GetBackgroundParent() const override;
|
|
|
|
already_AddRefed<nsISupports>
|
|
CreateStream(bool aReadOnly) override;
|
|
|
|
already_AddRefed<BlobImpl>
|
|
CreateBlobImpl() override;
|
|
|
|
private:
|
|
MutableFile(nsIFile* aFile,
|
|
Database* aDatabase,
|
|
FileInfo* aFileInfo);
|
|
|
|
~MutableFile() override;
|
|
|
|
PBackgroundFileHandleParent*
|
|
AllocPBackgroundFileHandleParent(const FileMode& aMode) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvPBackgroundFileHandleConstructor(PBackgroundFileHandleParent* aActor,
|
|
const FileMode& aMode) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvGetFileId(int64_t* aFileId) override;
|
|
};
|
|
|
|
class FactoryOp
|
|
: public DatabaseOperationBase
|
|
, public OpenDirectoryListener
|
|
, public PBackgroundIDBFactoryRequestParent
|
|
{
|
|
public:
|
|
struct MaybeBlockedDatabaseInfo;
|
|
|
|
protected:
|
|
enum class State
|
|
{
|
|
// Just created on the PBackground thread, dispatched to the main thread.
|
|
// Next step is either SendingResults if permission is denied,
|
|
// PermissionChallenge if the permission is unknown, or FinishOpen
|
|
// if permission is granted.
|
|
Initial,
|
|
|
|
// Sending a permission challenge message to the child on the PBackground
|
|
// thread. Next step is PermissionRetry.
|
|
PermissionChallenge,
|
|
|
|
// Retrying permission check after a challenge on the main thread. Next step
|
|
// is either SendingResults if permission is denied or FinishOpen
|
|
// if permission is granted.
|
|
PermissionRetry,
|
|
|
|
// Opening directory or initializing quota manager on the PBackground
|
|
// thread. Next step is either DirectoryOpenPending if quota manager is
|
|
// already initialized or QuotaManagerPending if quota manager needs to be
|
|
// initialized.
|
|
FinishOpen,
|
|
|
|
// Waiting for quota manager initialization to complete on the PBackground
|
|
// thread. Next step is either SendingResults if initialization failed or
|
|
// DirectoryOpenPending if initialization succeeded.
|
|
QuotaManagerPending,
|
|
|
|
// Waiting for directory open allowed on the PBackground thread. The next
|
|
// step is either SendingResults if directory lock failed to acquire, or
|
|
// DatabaseOpenPending if directory lock is acquired.
|
|
DirectoryOpenPending,
|
|
|
|
// Waiting for database open allowed on the PBackground thread. The next
|
|
// step is DatabaseWorkOpen.
|
|
DatabaseOpenPending,
|
|
|
|
// Waiting to do/doing work on the QuotaManager IO thread. Its next step is
|
|
// either BeginVersionChange if the requested version doesn't match the
|
|
// existing database version or SendingResults if the versions match.
|
|
DatabaseWorkOpen,
|
|
|
|
// Starting a version change transaction or deleting a database on the
|
|
// PBackground thread. We need to notify other databases that a version
|
|
// change is about to happen, and maybe tell the request that a version
|
|
// change has been blocked. If databases are notified then the next step is
|
|
// WaitingForOtherDatabasesToClose. Otherwise the next step is
|
|
// WaitingForTransactionsToComplete.
|
|
BeginVersionChange,
|
|
|
|
// Waiting for other databases to close on the PBackground thread. This
|
|
// state may persist until all databases are closed. The next state is
|
|
// WaitingForTransactionsToComplete.
|
|
WaitingForOtherDatabasesToClose,
|
|
|
|
// Waiting for all transactions that could interfere with this operation to
|
|
// complete on the PBackground thread. Next state is
|
|
// DatabaseWorkVersionChange.
|
|
WaitingForTransactionsToComplete,
|
|
|
|
// Waiting to do/doing work on the "work thread". This involves waiting for
|
|
// the VersionChangeOp (OpenDatabaseOp and DeleteDatabaseOp each have a
|
|
// different implementation) to do its work. Eventually the state will
|
|
// transition to SendingResults.
|
|
DatabaseWorkVersionChange,
|
|
|
|
// Waiting to send/sending results on the PBackground thread. Next step is
|
|
// Completed.
|
|
SendingResults,
|
|
|
|
// All done.
|
|
Completed
|
|
};
|
|
|
|
// Must be released on the background thread!
|
|
RefPtr<Factory> mFactory;
|
|
|
|
// Must be released on the main thread!
|
|
RefPtr<ContentParent> mContentParent;
|
|
|
|
// Must be released on the main thread!
|
|
RefPtr<DirectoryLock> mDirectoryLock;
|
|
|
|
RefPtr<FactoryOp> mDelayedOp;
|
|
nsTArray<MaybeBlockedDatabaseInfo> mMaybeBlockedDatabases;
|
|
|
|
const CommonFactoryRequestParams mCommonParams;
|
|
nsCString mSuffix;
|
|
nsCString mGroup;
|
|
nsCString mOrigin;
|
|
nsCString mDatabaseId;
|
|
nsString mDatabaseFilePath;
|
|
State mState;
|
|
bool mIsApp;
|
|
bool mEnforcingQuota;
|
|
const bool mDeleting;
|
|
bool mBlockedDatabaseOpen;
|
|
bool mChromeWriteAccessAllowed;
|
|
bool mFileHandleDisabled;
|
|
|
|
public:
|
|
void
|
|
NoteDatabaseBlocked(Database* aDatabase);
|
|
|
|
virtual void
|
|
NoteDatabaseClosed(Database* aDatabase) = 0;
|
|
|
|
#ifdef DEBUG
|
|
bool
|
|
HasBlockedDatabases() const
|
|
{
|
|
return !mMaybeBlockedDatabases.IsEmpty();
|
|
}
|
|
#endif
|
|
|
|
const nsString&
|
|
DatabaseFilePath() const
|
|
{
|
|
return mDatabaseFilePath;
|
|
}
|
|
|
|
protected:
|
|
FactoryOp(Factory* aFactory,
|
|
already_AddRefed<ContentParent> aContentParent,
|
|
const CommonFactoryRequestParams& aCommonParams,
|
|
bool aDeleting);
|
|
|
|
~FactoryOp() override
|
|
{
|
|
// Normally this would be out-of-line since it is a virtual function but
|
|
// MSVC 2010 fails to link for some reason if it is not inlined here...
|
|
MOZ_ASSERT_IF(OperationMayProceed(),
|
|
mState == State::Initial || mState == State::Completed);
|
|
}
|
|
|
|
nsresult
|
|
Open();
|
|
|
|
nsresult
|
|
ChallengePermission();
|
|
|
|
nsresult
|
|
RetryCheckPermission();
|
|
|
|
nsresult
|
|
DirectoryOpen();
|
|
|
|
nsresult
|
|
SendToIOThread();
|
|
|
|
void
|
|
WaitForTransactions();
|
|
|
|
void
|
|
FinishSendResults();
|
|
|
|
nsresult
|
|
SendVersionChangeMessages(DatabaseActorInfo* aDatabaseActorInfo,
|
|
Database* aOpeningDatabase,
|
|
uint64_t aOldVersion,
|
|
const NullableVersion& aNewVersion);
|
|
|
|
// Methods that subclasses must implement.
|
|
virtual nsresult
|
|
DatabaseOpen() = 0;
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork() = 0;
|
|
|
|
virtual nsresult
|
|
BeginVersionChange() = 0;
|
|
|
|
virtual nsresult
|
|
DispatchToWorkThread() = 0;
|
|
|
|
virtual void
|
|
SendResults() = 0;
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
// Common nsIRunnable implementation that subclasses may not override.
|
|
NS_IMETHOD
|
|
Run() final;
|
|
|
|
// OpenDirectoryListener overrides.
|
|
void
|
|
DirectoryLockAcquired(DirectoryLock* aLock) override;
|
|
|
|
void
|
|
DirectoryLockFailed() override;
|
|
|
|
// IPDL methods.
|
|
void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvPermissionRetry() override;
|
|
|
|
virtual void
|
|
SendBlockedNotification() = 0;
|
|
|
|
private:
|
|
nsresult
|
|
CheckPermission(ContentParent* aContentParent,
|
|
PermissionRequestBase::PermissionValue* aPermission);
|
|
|
|
static bool
|
|
CheckAtLeastOneAppHasPermission(ContentParent* aContentParent,
|
|
const nsACString& aPermissionString);
|
|
|
|
nsresult
|
|
FinishOpen();
|
|
|
|
nsresult
|
|
QuotaManagerOpen();
|
|
|
|
nsresult
|
|
OpenDirectory();
|
|
|
|
// Test whether this FactoryOp needs to wait for the given op.
|
|
bool
|
|
MustWaitFor(const FactoryOp& aExistingOp);
|
|
};
|
|
|
|
struct FactoryOp::MaybeBlockedDatabaseInfo final
|
|
{
|
|
RefPtr<Database> mDatabase;
|
|
bool mBlocked;
|
|
|
|
MOZ_IMPLICIT MaybeBlockedDatabaseInfo(Database* aDatabase)
|
|
: mDatabase(aDatabase)
|
|
, mBlocked(false)
|
|
{
|
|
MOZ_ASSERT(aDatabase);
|
|
|
|
MOZ_COUNT_CTOR(FactoryOp::MaybeBlockedDatabaseInfo);
|
|
}
|
|
|
|
~MaybeBlockedDatabaseInfo()
|
|
{
|
|
MOZ_COUNT_DTOR(FactoryOp::MaybeBlockedDatabaseInfo);
|
|
}
|
|
|
|
bool
|
|
operator==(const MaybeBlockedDatabaseInfo& aOther) const
|
|
{
|
|
return mDatabase == aOther.mDatabase;
|
|
}
|
|
|
|
bool
|
|
operator<(const MaybeBlockedDatabaseInfo& aOther) const
|
|
{
|
|
return mDatabase < aOther.mDatabase;
|
|
}
|
|
|
|
Database*
|
|
operator->() MOZ_NO_ADDREF_RELEASE_ON_RETURN
|
|
{
|
|
return mDatabase;
|
|
}
|
|
};
|
|
|
|
class OpenDatabaseOp final
|
|
: public FactoryOp
|
|
{
|
|
friend class Database;
|
|
friend class VersionChangeTransaction;
|
|
|
|
class VersionChangeOp;
|
|
|
|
Maybe<ContentParentId> mOptionalContentParentId;
|
|
|
|
RefPtr<FullDatabaseMetadata> mMetadata;
|
|
|
|
uint64_t mRequestedVersion;
|
|
RefPtr<FileManager> mFileManager;
|
|
|
|
RefPtr<Database> mDatabase;
|
|
RefPtr<VersionChangeTransaction> mVersionChangeTransaction;
|
|
|
|
// This is only set while a VersionChangeOp is live. It holds a strong
|
|
// reference to its OpenDatabaseOp object so this is a weak pointer to avoid
|
|
// cycles.
|
|
VersionChangeOp* mVersionChangeOp;
|
|
|
|
uint32_t mTelemetryId;
|
|
|
|
public:
|
|
OpenDatabaseOp(Factory* aFactory,
|
|
already_AddRefed<ContentParent> aContentParent,
|
|
const CommonFactoryRequestParams& aParams);
|
|
|
|
bool
|
|
IsOtherProcessActor() const
|
|
{
|
|
return mOptionalContentParentId.isSome();
|
|
}
|
|
|
|
private:
|
|
~OpenDatabaseOp() override
|
|
{
|
|
MOZ_ASSERT(!mVersionChangeOp);
|
|
}
|
|
|
|
nsresult
|
|
LoadDatabaseInformation(mozIStorageConnection* aConnection);
|
|
|
|
nsresult
|
|
SendUpgradeNeeded();
|
|
|
|
void
|
|
EnsureDatabaseActor();
|
|
|
|
nsresult
|
|
EnsureDatabaseActorIsAlive();
|
|
|
|
void
|
|
MetadataToSpec(DatabaseSpec& aSpec);
|
|
|
|
void
|
|
AssertMetadataConsistency(const FullDatabaseMetadata* aMetadata)
|
|
#ifdef DEBUG
|
|
;
|
|
#else
|
|
{ }
|
|
#endif
|
|
|
|
void
|
|
ConnectionClosedCallback();
|
|
|
|
void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
nsresult
|
|
DatabaseOpen() override;
|
|
|
|
nsresult
|
|
DoDatabaseWork() override;
|
|
|
|
nsresult
|
|
BeginVersionChange() override;
|
|
|
|
void
|
|
NoteDatabaseClosed(Database* aDatabase) override;
|
|
|
|
void
|
|
SendBlockedNotification() override;
|
|
|
|
nsresult
|
|
DispatchToWorkThread() override;
|
|
|
|
void
|
|
SendResults() override;
|
|
|
|
#ifdef ENABLE_INTL_API
|
|
static nsresult
|
|
UpdateLocaleAwareIndex(mozIStorageConnection* aConnection,
|
|
const IndexMetadata& aIndexMetadata,
|
|
const nsCString& aLocale);
|
|
#endif
|
|
};
|
|
|
|
class OpenDatabaseOp::VersionChangeOp final
|
|
: public TransactionDatabaseOperationBase
|
|
{
|
|
friend class OpenDatabaseOp;
|
|
|
|
RefPtr<OpenDatabaseOp> mOpenDatabaseOp;
|
|
const uint64_t mRequestedVersion;
|
|
uint64_t mPreviousVersion;
|
|
|
|
private:
|
|
explicit
|
|
VersionChangeOp(OpenDatabaseOp* aOpenDatabaseOp)
|
|
: TransactionDatabaseOperationBase(
|
|
aOpenDatabaseOp->mVersionChangeTransaction,
|
|
aOpenDatabaseOp->LoggingSerialNumber())
|
|
, mOpenDatabaseOp(aOpenDatabaseOp)
|
|
, mRequestedVersion(aOpenDatabaseOp->mRequestedVersion)
|
|
, mPreviousVersion(aOpenDatabaseOp->mMetadata->mCommonMetadata.version())
|
|
{
|
|
MOZ_ASSERT(aOpenDatabaseOp);
|
|
MOZ_ASSERT(mRequestedVersion);
|
|
}
|
|
|
|
~VersionChangeOp() override
|
|
{
|
|
MOZ_ASSERT(!mOpenDatabaseOp);
|
|
}
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
nsresult
|
|
SendSuccessResult() override;
|
|
|
|
bool
|
|
SendFailureResult(nsresult aResultCode) override;
|
|
|
|
void
|
|
Cleanup() override;
|
|
};
|
|
|
|
class DeleteDatabaseOp final
|
|
: public FactoryOp
|
|
{
|
|
class VersionChangeOp;
|
|
|
|
nsString mDatabaseDirectoryPath;
|
|
nsString mDatabaseFilenameBase;
|
|
uint64_t mPreviousVersion;
|
|
|
|
public:
|
|
DeleteDatabaseOp(Factory* aFactory,
|
|
already_AddRefed<ContentParent> aContentParent,
|
|
const CommonFactoryRequestParams& aParams)
|
|
: FactoryOp(aFactory, Move(aContentParent), aParams, /* aDeleting */ true)
|
|
, mPreviousVersion(0)
|
|
{ }
|
|
|
|
private:
|
|
~DeleteDatabaseOp() override = default;
|
|
|
|
void
|
|
LoadPreviousVersion(nsIFile* aDatabaseFile);
|
|
|
|
nsresult
|
|
DatabaseOpen() override;
|
|
|
|
nsresult
|
|
DoDatabaseWork() override;
|
|
|
|
nsresult
|
|
BeginVersionChange() override;
|
|
|
|
void
|
|
NoteDatabaseClosed(Database* aDatabase) override;
|
|
|
|
void
|
|
SendBlockedNotification() override;
|
|
|
|
nsresult
|
|
DispatchToWorkThread() override;
|
|
|
|
void
|
|
SendResults() override;
|
|
};
|
|
|
|
class DeleteDatabaseOp::VersionChangeOp final
|
|
: public DatabaseOperationBase
|
|
{
|
|
friend class DeleteDatabaseOp;
|
|
|
|
RefPtr<DeleteDatabaseOp> mDeleteDatabaseOp;
|
|
|
|
private:
|
|
explicit
|
|
VersionChangeOp(DeleteDatabaseOp* aDeleteDatabaseOp)
|
|
: DatabaseOperationBase(aDeleteDatabaseOp->BackgroundChildLoggingId(),
|
|
aDeleteDatabaseOp->LoggingSerialNumber())
|
|
, mDeleteDatabaseOp(aDeleteDatabaseOp)
|
|
{
|
|
MOZ_ASSERT(aDeleteDatabaseOp);
|
|
MOZ_ASSERT(!aDeleteDatabaseOp->mDatabaseDirectoryPath.IsEmpty());
|
|
}
|
|
|
|
~VersionChangeOp() override = default;
|
|
|
|
nsresult
|
|
RunOnIOThread();
|
|
|
|
void
|
|
RunOnOwningThread();
|
|
|
|
nsresult
|
|
DeleteFile(nsIFile* aDirectory,
|
|
const nsAString& aFilename,
|
|
QuotaManager* aQuotaManager);
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
};
|
|
|
|
class DatabaseOp
|
|
: public DatabaseOperationBase
|
|
, public PBackgroundIDBDatabaseRequestParent
|
|
{
|
|
protected:
|
|
RefPtr<Database> mDatabase;
|
|
|
|
enum class State
|
|
{
|
|
// Just created on the PBackground thread, dispatched to the main thread.
|
|
// Next step is DatabaseWork.
|
|
Initial,
|
|
|
|
// Waiting to do/doing work on the QuotaManager IO thread. Next step is
|
|
// SendingResults.
|
|
DatabaseWork,
|
|
|
|
// Waiting to send/sending results on the PBackground thread. Next step is
|
|
// Completed.
|
|
SendingResults,
|
|
|
|
// All done.
|
|
Completed
|
|
};
|
|
|
|
State mState;
|
|
|
|
public:
|
|
void
|
|
RunImmediately()
|
|
{
|
|
MOZ_ASSERT(mState == State::Initial);
|
|
|
|
Unused << this->Run();
|
|
}
|
|
|
|
protected:
|
|
DatabaseOp(Database* aDatabase);
|
|
|
|
~DatabaseOp() override
|
|
{
|
|
MOZ_ASSERT_IF(OperationMayProceed(),
|
|
mState == State::Initial || mState == State::Completed);
|
|
}
|
|
|
|
nsresult
|
|
SendToIOThread();
|
|
|
|
// Methods that subclasses must implement.
|
|
virtual nsresult
|
|
DoDatabaseWork() = 0;
|
|
|
|
virtual void
|
|
SendResults() = 0;
|
|
|
|
// Common nsIRunnable implementation that subclasses may not override.
|
|
NS_IMETHOD
|
|
Run() final;
|
|
|
|
// IPDL methods.
|
|
void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
};
|
|
|
|
class CreateFileOp final
|
|
: public DatabaseOp
|
|
{
|
|
const CreateFileParams mParams;
|
|
|
|
RefPtr<FileInfo> mFileInfo;
|
|
|
|
public:
|
|
CreateFileOp(Database* aDatabase,
|
|
const DatabaseRequestParams& aParams);
|
|
|
|
private:
|
|
~CreateFileOp() override = default;
|
|
|
|
nsresult
|
|
CreateMutableFile(MutableFile** aMutableFile);
|
|
|
|
nsresult
|
|
DoDatabaseWork() override;
|
|
|
|
void
|
|
SendResults() override;
|
|
};
|
|
|
|
class VersionChangeTransactionOp
|
|
: public TransactionDatabaseOperationBase
|
|
{
|
|
public:
|
|
void
|
|
Cleanup() override;
|
|
|
|
protected:
|
|
explicit VersionChangeTransactionOp(VersionChangeTransaction* aTransaction)
|
|
: TransactionDatabaseOperationBase(aTransaction)
|
|
{ }
|
|
|
|
~VersionChangeTransactionOp() override = default;
|
|
|
|
private:
|
|
nsresult
|
|
SendSuccessResult() override;
|
|
|
|
bool
|
|
SendFailureResult(nsresult aResultCode) override;
|
|
};
|
|
|
|
class CreateObjectStoreOp final
|
|
: public VersionChangeTransactionOp
|
|
{
|
|
friend class VersionChangeTransaction;
|
|
|
|
const ObjectStoreMetadata mMetadata;
|
|
|
|
private:
|
|
// Only created by VersionChangeTransaction.
|
|
CreateObjectStoreOp(VersionChangeTransaction* aTransaction,
|
|
const ObjectStoreMetadata& aMetadata)
|
|
: VersionChangeTransactionOp(aTransaction)
|
|
, mMetadata(aMetadata)
|
|
{
|
|
MOZ_ASSERT(aMetadata.id());
|
|
}
|
|
|
|
~CreateObjectStoreOp() override = default;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
};
|
|
|
|
class DeleteObjectStoreOp final
|
|
: public VersionChangeTransactionOp
|
|
{
|
|
friend class VersionChangeTransaction;
|
|
|
|
const RefPtr<FullObjectStoreMetadata> mMetadata;
|
|
const bool mIsLastObjectStore;
|
|
|
|
private:
|
|
// Only created by VersionChangeTransaction.
|
|
DeleteObjectStoreOp(VersionChangeTransaction* aTransaction,
|
|
FullObjectStoreMetadata* const aMetadata,
|
|
const bool aIsLastObjectStore)
|
|
: VersionChangeTransactionOp(aTransaction)
|
|
, mMetadata(aMetadata)
|
|
, mIsLastObjectStore(aIsLastObjectStore)
|
|
{
|
|
MOZ_ASSERT(aMetadata->mCommonMetadata.id());
|
|
}
|
|
|
|
~DeleteObjectStoreOp() override = default;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
};
|
|
|
|
class RenameObjectStoreOp final
|
|
: public VersionChangeTransactionOp
|
|
{
|
|
friend class VersionChangeTransaction;
|
|
|
|
const int64_t mId;
|
|
const nsString mNewName;
|
|
|
|
private:
|
|
// Only created by VersionChangeTransaction.
|
|
RenameObjectStoreOp(VersionChangeTransaction* aTransaction,
|
|
FullObjectStoreMetadata* const aMetadata)
|
|
: VersionChangeTransactionOp(aTransaction)
|
|
, mId(aMetadata->mCommonMetadata.id())
|
|
, mNewName(aMetadata->mCommonMetadata.name())
|
|
{
|
|
MOZ_ASSERT(mId);
|
|
}
|
|
|
|
~RenameObjectStoreOp() override = default;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
};
|
|
|
|
class CreateIndexOp final
|
|
: public VersionChangeTransactionOp
|
|
{
|
|
friend class VersionChangeTransaction;
|
|
|
|
class ThreadLocalJSContext;
|
|
class UpdateIndexDataValuesFunction;
|
|
|
|
static const unsigned int kBadThreadLocalIndex =
|
|
static_cast<unsigned int>(-1);
|
|
|
|
static unsigned int sThreadLocalIndex;
|
|
|
|
const IndexMetadata mMetadata;
|
|
Maybe<UniqueIndexTable> mMaybeUniqueIndexTable;
|
|
RefPtr<FileManager> mFileManager;
|
|
const nsCString mDatabaseId;
|
|
const uint64_t mObjectStoreId;
|
|
|
|
private:
|
|
// Only created by VersionChangeTransaction.
|
|
CreateIndexOp(VersionChangeTransaction* aTransaction,
|
|
const int64_t aObjectStoreId,
|
|
const IndexMetadata& aMetadata);
|
|
|
|
~CreateIndexOp() override = default;
|
|
|
|
nsresult
|
|
InsertDataFromObjectStore(DatabaseConnection* aConnection);
|
|
|
|
nsresult
|
|
InsertDataFromObjectStoreInternal(DatabaseConnection* aConnection);
|
|
|
|
bool
|
|
Init(TransactionBase* aTransaction) override;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
};
|
|
|
|
class NormalJSContext
|
|
{
|
|
friend class nsAutoPtr<NormalJSContext>;
|
|
|
|
static const JSClass sGlobalClass;
|
|
static const uint32_t kContextHeapSize = 768 * 1024;
|
|
|
|
JSContext* mContext;
|
|
JSObject* mGlobal;
|
|
|
|
public:
|
|
static NormalJSContext*
|
|
Create();
|
|
|
|
JSContext*
|
|
Context() const
|
|
{
|
|
return mContext;
|
|
}
|
|
|
|
JSObject*
|
|
Global() const
|
|
{
|
|
return mGlobal;
|
|
}
|
|
|
|
protected:
|
|
NormalJSContext()
|
|
: mContext(nullptr)
|
|
, mGlobal(nullptr)
|
|
{
|
|
MOZ_COUNT_CTOR(NormalJSContext);
|
|
}
|
|
|
|
~NormalJSContext()
|
|
{
|
|
MOZ_COUNT_DTOR(NormalJSContext);
|
|
|
|
if (mContext) {
|
|
JS_DestroyContext(mContext);
|
|
}
|
|
}
|
|
|
|
bool
|
|
Init();
|
|
};
|
|
|
|
class CreateIndexOp::ThreadLocalJSContext final
|
|
: public NormalJSContext
|
|
{
|
|
friend class CreateIndexOp;
|
|
friend class nsAutoPtr<ThreadLocalJSContext>;
|
|
|
|
public:
|
|
static ThreadLocalJSContext*
|
|
GetOrCreate();
|
|
|
|
private:
|
|
ThreadLocalJSContext()
|
|
{
|
|
MOZ_COUNT_CTOR(CreateIndexOp::ThreadLocalJSContext);
|
|
}
|
|
|
|
~ThreadLocalJSContext()
|
|
{
|
|
MOZ_COUNT_DTOR(CreateIndexOp::ThreadLocalJSContext);
|
|
}
|
|
};
|
|
|
|
class CreateIndexOp::UpdateIndexDataValuesFunction final
|
|
: public mozIStorageFunction
|
|
{
|
|
RefPtr<CreateIndexOp> mOp;
|
|
RefPtr<DatabaseConnection> mConnection;
|
|
JSContext* mCx;
|
|
|
|
public:
|
|
UpdateIndexDataValuesFunction(CreateIndexOp* aOp,
|
|
DatabaseConnection* aConnection,
|
|
JSContext* aCx)
|
|
: mOp(aOp)
|
|
, mConnection(aConnection)
|
|
, mCx(aCx)
|
|
{
|
|
MOZ_ASSERT(aOp);
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aCx);
|
|
}
|
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
private:
|
|
~UpdateIndexDataValuesFunction() = default;
|
|
|
|
NS_DECL_MOZISTORAGEFUNCTION
|
|
};
|
|
|
|
class DeleteIndexOp final
|
|
: public VersionChangeTransactionOp
|
|
{
|
|
friend class VersionChangeTransaction;
|
|
|
|
const int64_t mObjectStoreId;
|
|
const int64_t mIndexId;
|
|
const bool mUnique;
|
|
const bool mIsLastIndex;
|
|
|
|
private:
|
|
// Only created by VersionChangeTransaction.
|
|
DeleteIndexOp(VersionChangeTransaction* aTransaction,
|
|
const int64_t aObjectStoreId,
|
|
const int64_t aIndexId,
|
|
const bool aUnique,
|
|
const bool aIsLastIndex);
|
|
|
|
~DeleteIndexOp() override = default;
|
|
|
|
nsresult
|
|
RemoveReferencesToIndex(DatabaseConnection* aConnection,
|
|
const Key& aObjectDataKey,
|
|
nsTArray<IndexDataValue>& aIndexValues);
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
};
|
|
|
|
class RenameIndexOp final
|
|
: public VersionChangeTransactionOp
|
|
{
|
|
friend class VersionChangeTransaction;
|
|
|
|
const int64_t mObjectStoreId;
|
|
const int64_t mIndexId;
|
|
const nsString mNewName;
|
|
|
|
private:
|
|
// Only created by VersionChangeTransaction.
|
|
RenameIndexOp(VersionChangeTransaction* aTransaction,
|
|
FullIndexMetadata* const aMetadata,
|
|
int64_t aObjectStoreId)
|
|
: VersionChangeTransactionOp(aTransaction)
|
|
, mObjectStoreId(aObjectStoreId)
|
|
, mIndexId(aMetadata->mCommonMetadata.id())
|
|
, mNewName(aMetadata->mCommonMetadata.name())
|
|
{
|
|
MOZ_ASSERT(mIndexId);
|
|
}
|
|
|
|
~RenameIndexOp() override = default;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
};
|
|
|
|
class NormalTransactionOp
|
|
: public TransactionDatabaseOperationBase
|
|
, public PBackgroundIDBRequestParent
|
|
{
|
|
#ifdef DEBUG
|
|
bool mResponseSent;
|
|
#endif
|
|
|
|
public:
|
|
void
|
|
Cleanup() override;
|
|
|
|
protected:
|
|
explicit NormalTransactionOp(TransactionBase* aTransaction)
|
|
: TransactionDatabaseOperationBase(aTransaction)
|
|
#ifdef DEBUG
|
|
, mResponseSent(false)
|
|
#endif
|
|
{ }
|
|
|
|
~NormalTransactionOp() override = default;
|
|
|
|
// An overload of DatabaseOperationBase's function that can avoid doing extra
|
|
// work on non-versionchange transactions.
|
|
static nsresult
|
|
ObjectStoreHasIndexes(NormalTransactionOp* aOp,
|
|
DatabaseConnection* aConnection,
|
|
const int64_t aObjectStoreId,
|
|
const bool aMayHaveIndexes,
|
|
bool* aHasIndexes);
|
|
|
|
virtual nsresult
|
|
GetPreprocessParams(PreprocessParams& aParams);
|
|
|
|
|
|
// Subclasses use this override to set the IPDL response value.
|
|
virtual void
|
|
GetResponse(RequestResponse& aResponse) = 0;
|
|
|
|
private:
|
|
nsresult
|
|
SendPreprocessInfo() override;
|
|
|
|
nsresult
|
|
SendSuccessResult() override;
|
|
|
|
bool
|
|
SendFailureResult(nsresult aResultCode) override;
|
|
|
|
// IPDL methods.
|
|
void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvContinue(const PreprocessResponse& aResponse) override;
|
|
};
|
|
|
|
class ObjectStoreAddOrPutRequestOp final
|
|
: public NormalTransactionOp
|
|
{
|
|
friend class TransactionBase;
|
|
|
|
typedef mozilla::dom::quota::PersistenceType PersistenceType;
|
|
|
|
struct StoredFileInfo;
|
|
class SCInputStream;
|
|
|
|
const ObjectStoreAddPutParams mParams;
|
|
Maybe<UniqueIndexTable> mUniqueIndexTable;
|
|
|
|
// This must be non-const so that we can update the mNextAutoIncrementId field
|
|
// if we are modifying an autoIncrement objectStore.
|
|
RefPtr<FullObjectStoreMetadata> mMetadata;
|
|
|
|
FallibleTArray<StoredFileInfo> mStoredFileInfos;
|
|
|
|
Key mResponse;
|
|
const nsCString mGroup;
|
|
const nsCString mOrigin;
|
|
const PersistenceType mPersistenceType;
|
|
const bool mOverwrite;
|
|
bool mObjectStoreMayHaveIndexes;
|
|
bool mDataOverThreshold;
|
|
|
|
private:
|
|
// Only created by TransactionBase.
|
|
ObjectStoreAddOrPutRequestOp(TransactionBase* aTransaction,
|
|
const RequestParams& aParams);
|
|
|
|
~ObjectStoreAddOrPutRequestOp() override = default;
|
|
|
|
nsresult
|
|
RemoveOldIndexDataValues(DatabaseConnection* aConnection);
|
|
|
|
bool
|
|
Init(TransactionBase* aTransaction) override;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
void
|
|
GetResponse(RequestResponse& aResponse) override;
|
|
|
|
void
|
|
Cleanup() override;
|
|
};
|
|
|
|
struct ObjectStoreAddOrPutRequestOp::StoredFileInfo final
|
|
{
|
|
RefPtr<DatabaseFile> mFileActor;
|
|
RefPtr<FileInfo> mFileInfo;
|
|
// A non-Blob-backed inputstream to write to disk. If null, mFileActor may
|
|
// still have a stream for us to write.
|
|
nsCOMPtr<nsIInputStream> mInputStream;
|
|
StructuredCloneFile::FileType mType;
|
|
|
|
StoredFileInfo()
|
|
: mType(StructuredCloneFile::eBlob)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
MOZ_COUNT_CTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
|
|
}
|
|
|
|
~StoredFileInfo()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
MOZ_COUNT_DTOR(ObjectStoreAddOrPutRequestOp::StoredFileInfo);
|
|
}
|
|
|
|
void
|
|
Serialize(nsString& aText)
|
|
{
|
|
MOZ_ASSERT(mFileInfo);
|
|
|
|
const int64_t id = mFileInfo->Id();
|
|
|
|
switch (mType) {
|
|
case StructuredCloneFile::eBlob:
|
|
aText.AppendInt(id);
|
|
break;
|
|
|
|
case StructuredCloneFile::eMutableFile:
|
|
aText.AppendInt(-id);
|
|
break;
|
|
|
|
case StructuredCloneFile::eStructuredClone:
|
|
aText.Append('.');
|
|
aText.AppendInt(id);
|
|
break;
|
|
|
|
case StructuredCloneFile::eWasmBytecode:
|
|
aText.Append('/');
|
|
aText.AppendInt(id);
|
|
break;
|
|
|
|
case StructuredCloneFile::eWasmCompiled:
|
|
aText.Append('\\');
|
|
aText.AppendInt(id);
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
}
|
|
};
|
|
|
|
class ObjectStoreAddOrPutRequestOp::SCInputStream final
|
|
: public nsIInputStream
|
|
{
|
|
const JSStructuredCloneData& mData;
|
|
JSStructuredCloneData::IterImpl mIter;
|
|
|
|
public:
|
|
explicit SCInputStream(const JSStructuredCloneData& aData)
|
|
: mData(aData)
|
|
, mIter(aData.Iter())
|
|
{ }
|
|
|
|
private:
|
|
virtual ~SCInputStream() = default;
|
|
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
NS_DECL_NSIINPUTSTREAM
|
|
};
|
|
|
|
class ObjectStoreGetRequestOp final
|
|
: public NormalTransactionOp
|
|
{
|
|
friend class TransactionBase;
|
|
|
|
const uint32_t mObjectStoreId;
|
|
RefPtr<Database> mDatabase;
|
|
const OptionalKeyRange mOptionalKeyRange;
|
|
AutoTArray<StructuredCloneReadInfo, 1> mResponse;
|
|
PBackgroundParent* mBackgroundParent;
|
|
uint32_t mPreprocessInfoCount;
|
|
const uint32_t mLimit;
|
|
const bool mGetAll;
|
|
|
|
private:
|
|
// Only created by TransactionBase.
|
|
ObjectStoreGetRequestOp(TransactionBase* aTransaction,
|
|
const RequestParams& aParams,
|
|
bool aGetAll);
|
|
|
|
~ObjectStoreGetRequestOp() override = default;
|
|
|
|
template <bool aForPreprocess, typename T>
|
|
nsresult
|
|
ConvertResponse(StructuredCloneReadInfo& aInfo, T& aResult);
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
bool
|
|
HasPreprocessInfo() override;
|
|
|
|
nsresult
|
|
GetPreprocessParams(PreprocessParams& aParams) override;
|
|
|
|
void
|
|
GetResponse(RequestResponse& aResponse) override;
|
|
};
|
|
|
|
class ObjectStoreGetKeyRequestOp final
|
|
: public NormalTransactionOp
|
|
{
|
|
friend class TransactionBase;
|
|
|
|
const uint32_t mObjectStoreId;
|
|
const OptionalKeyRange mOptionalKeyRange;
|
|
const uint32_t mLimit;
|
|
const bool mGetAll;
|
|
FallibleTArray<Key> mResponse;
|
|
|
|
private:
|
|
// Only created by TransactionBase.
|
|
ObjectStoreGetKeyRequestOp(TransactionBase* aTransaction,
|
|
const RequestParams& aParams,
|
|
bool aGetAll);
|
|
|
|
~ObjectStoreGetKeyRequestOp() override = default;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
void
|
|
GetResponse(RequestResponse& aResponse) override;
|
|
};
|
|
|
|
class ObjectStoreDeleteRequestOp final
|
|
: public NormalTransactionOp
|
|
{
|
|
friend class TransactionBase;
|
|
|
|
const ObjectStoreDeleteParams mParams;
|
|
ObjectStoreDeleteResponse mResponse;
|
|
bool mObjectStoreMayHaveIndexes;
|
|
|
|
private:
|
|
ObjectStoreDeleteRequestOp(TransactionBase* aTransaction,
|
|
const ObjectStoreDeleteParams& aParams);
|
|
|
|
~ObjectStoreDeleteRequestOp() override = default;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
void
|
|
GetResponse(RequestResponse& aResponse) override
|
|
{
|
|
aResponse = Move(mResponse);
|
|
}
|
|
};
|
|
|
|
class ObjectStoreClearRequestOp final
|
|
: public NormalTransactionOp
|
|
{
|
|
friend class TransactionBase;
|
|
|
|
const ObjectStoreClearParams mParams;
|
|
ObjectStoreClearResponse mResponse;
|
|
bool mObjectStoreMayHaveIndexes;
|
|
|
|
private:
|
|
ObjectStoreClearRequestOp(TransactionBase* aTransaction,
|
|
const ObjectStoreClearParams& aParams);
|
|
|
|
~ObjectStoreClearRequestOp() override = default;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
void
|
|
GetResponse(RequestResponse& aResponse) override
|
|
{
|
|
aResponse = Move(mResponse);
|
|
}
|
|
};
|
|
|
|
class ObjectStoreCountRequestOp final
|
|
: public NormalTransactionOp
|
|
{
|
|
friend class TransactionBase;
|
|
|
|
const ObjectStoreCountParams mParams;
|
|
ObjectStoreCountResponse mResponse;
|
|
|
|
private:
|
|
ObjectStoreCountRequestOp(TransactionBase* aTransaction,
|
|
const ObjectStoreCountParams& aParams)
|
|
: NormalTransactionOp(aTransaction)
|
|
, mParams(aParams)
|
|
{ }
|
|
|
|
~ObjectStoreCountRequestOp() override = default;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
void
|
|
GetResponse(RequestResponse& aResponse) override
|
|
{
|
|
aResponse = Move(mResponse);
|
|
}
|
|
};
|
|
|
|
class IndexRequestOpBase
|
|
: public NormalTransactionOp
|
|
{
|
|
protected:
|
|
const RefPtr<FullIndexMetadata> mMetadata;
|
|
|
|
protected:
|
|
IndexRequestOpBase(TransactionBase* aTransaction,
|
|
const RequestParams& aParams)
|
|
: NormalTransactionOp(aTransaction)
|
|
, mMetadata(IndexMetadataForParams(aTransaction, aParams))
|
|
{ }
|
|
|
|
|
|
~IndexRequestOpBase() override = default;
|
|
|
|
private:
|
|
static already_AddRefed<FullIndexMetadata>
|
|
IndexMetadataForParams(TransactionBase* aTransaction,
|
|
const RequestParams& aParams);
|
|
};
|
|
|
|
class IndexGetRequestOp final
|
|
: public IndexRequestOpBase
|
|
{
|
|
friend class TransactionBase;
|
|
|
|
RefPtr<Database> mDatabase;
|
|
const OptionalKeyRange mOptionalKeyRange;
|
|
AutoTArray<StructuredCloneReadInfo, 1> mResponse;
|
|
PBackgroundParent* mBackgroundParent;
|
|
const uint32_t mLimit;
|
|
const bool mGetAll;
|
|
|
|
private:
|
|
// Only created by TransactionBase.
|
|
IndexGetRequestOp(TransactionBase* aTransaction,
|
|
const RequestParams& aParams,
|
|
bool aGetAll);
|
|
|
|
~IndexGetRequestOp() override = default;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
void
|
|
GetResponse(RequestResponse& aResponse) override;
|
|
};
|
|
|
|
class IndexGetKeyRequestOp final
|
|
: public IndexRequestOpBase
|
|
{
|
|
friend class TransactionBase;
|
|
|
|
const OptionalKeyRange mOptionalKeyRange;
|
|
AutoTArray<Key, 1> mResponse;
|
|
const uint32_t mLimit;
|
|
const bool mGetAll;
|
|
|
|
private:
|
|
// Only created by TransactionBase.
|
|
IndexGetKeyRequestOp(TransactionBase* aTransaction,
|
|
const RequestParams& aParams,
|
|
bool aGetAll);
|
|
|
|
~IndexGetKeyRequestOp() override = default;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
void
|
|
GetResponse(RequestResponse& aResponse) override;
|
|
};
|
|
|
|
class IndexCountRequestOp final
|
|
: public IndexRequestOpBase
|
|
{
|
|
friend class TransactionBase;
|
|
|
|
const IndexCountParams mParams;
|
|
IndexCountResponse mResponse;
|
|
|
|
private:
|
|
// Only created by TransactionBase.
|
|
IndexCountRequestOp(TransactionBase* aTransaction,
|
|
const RequestParams& aParams)
|
|
: IndexRequestOpBase(aTransaction, aParams)
|
|
, mParams(aParams.get_IndexCountParams())
|
|
{ }
|
|
|
|
~IndexCountRequestOp() override = default;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
void
|
|
GetResponse(RequestResponse& aResponse) override
|
|
{
|
|
aResponse = Move(mResponse);
|
|
}
|
|
};
|
|
|
|
class Cursor final :
|
|
public PBackgroundIDBCursorParent
|
|
{
|
|
friend class TransactionBase;
|
|
|
|
class ContinueOp;
|
|
class CursorOpBase;
|
|
class OpenOp;
|
|
|
|
public:
|
|
typedef OpenCursorParams::Type Type;
|
|
|
|
private:
|
|
RefPtr<TransactionBase> mTransaction;
|
|
RefPtr<Database> mDatabase;
|
|
RefPtr<FileManager> mFileManager;
|
|
PBackgroundParent* mBackgroundParent;
|
|
|
|
// These should only be touched on the PBackground thread to check whether the
|
|
// objectStore or index has been deleted. Holding these saves a hash lookup
|
|
// for every call to continue()/advance().
|
|
RefPtr<FullObjectStoreMetadata> mObjectStoreMetadata;
|
|
RefPtr<FullIndexMetadata> mIndexMetadata;
|
|
|
|
const int64_t mObjectStoreId;
|
|
const int64_t mIndexId;
|
|
|
|
nsCString mContinueQuery;
|
|
nsCString mContinueToQuery;
|
|
nsCString mContinuePrimaryKeyQuery;
|
|
nsCString mLocale;
|
|
|
|
Key mKey;
|
|
Key mObjectKey;
|
|
Key mRangeKey;
|
|
Key mSortKey;
|
|
|
|
CursorOpBase* mCurrentlyRunningOp;
|
|
|
|
const Type mType;
|
|
const Direction mDirection;
|
|
|
|
const bool mUniqueIndex;
|
|
const bool mIsSameProcessActor;
|
|
bool mActorDestroyed;
|
|
|
|
public:
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Cursor)
|
|
|
|
private:
|
|
// Only created by TransactionBase.
|
|
Cursor(TransactionBase* aTransaction,
|
|
Type aType,
|
|
FullObjectStoreMetadata* aObjectStoreMetadata,
|
|
FullIndexMetadata* aIndexMetadata,
|
|
Direction aDirection);
|
|
|
|
// Reference counted.
|
|
~Cursor() override
|
|
{
|
|
MOZ_ASSERT(mActorDestroyed);
|
|
}
|
|
|
|
bool
|
|
VerifyRequestParams(const CursorRequestParams& aParams) const;
|
|
|
|
// Only called by TransactionBase.
|
|
bool
|
|
Start(const OpenCursorParams& aParams);
|
|
|
|
void
|
|
SendResponseInternal(
|
|
CursorResponse& aResponse,
|
|
const nsTArray<FallibleTArray<StructuredCloneFile>>& aFiles);
|
|
|
|
// Must call SendResponseInternal!
|
|
bool
|
|
SendResponse(const CursorResponse& aResponse) = delete;
|
|
|
|
// IPDL methods.
|
|
void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvDeleteMe() override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvContinue(const CursorRequestParams& aParams) override;
|
|
|
|
bool
|
|
IsLocaleAware() const {
|
|
return !mLocale.IsEmpty();
|
|
}
|
|
};
|
|
|
|
class Cursor::CursorOpBase
|
|
: public TransactionDatabaseOperationBase
|
|
{
|
|
protected:
|
|
RefPtr<Cursor> mCursor;
|
|
nsTArray<FallibleTArray<StructuredCloneFile>> mFiles;
|
|
|
|
CursorResponse mResponse;
|
|
|
|
#ifdef DEBUG
|
|
bool mResponseSent;
|
|
#endif
|
|
|
|
protected:
|
|
explicit CursorOpBase(Cursor* aCursor)
|
|
: TransactionDatabaseOperationBase(aCursor->mTransaction)
|
|
, mCursor(aCursor)
|
|
#ifdef DEBUG
|
|
, mResponseSent(false)
|
|
#endif
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aCursor);
|
|
}
|
|
|
|
|
|
~CursorOpBase() override = default;
|
|
|
|
bool
|
|
SendFailureResult(nsresult aResultCode) override;
|
|
|
|
void
|
|
Cleanup() override;
|
|
|
|
nsresult
|
|
PopulateResponseFromStatement(DatabaseConnection::CachedStatement& aStmt,
|
|
bool aInitializeResponse);
|
|
};
|
|
|
|
class Cursor::OpenOp final
|
|
: public Cursor::CursorOpBase
|
|
{
|
|
friend class Cursor;
|
|
|
|
const OptionalKeyRange mOptionalKeyRange;
|
|
|
|
private:
|
|
// Only created by Cursor.
|
|
OpenOp(Cursor* aCursor,
|
|
const OptionalKeyRange& aOptionalKeyRange)
|
|
: CursorOpBase(aCursor)
|
|
, mOptionalKeyRange(aOptionalKeyRange)
|
|
{ }
|
|
|
|
// Reference counted.
|
|
~OpenOp() override = default;
|
|
|
|
void
|
|
GetRangeKeyInfo(bool aLowerBound, Key* aKey, bool* aOpen);
|
|
|
|
nsresult
|
|
DoObjectStoreDatabaseWork(DatabaseConnection* aConnection);
|
|
|
|
nsresult
|
|
DoObjectStoreKeyDatabaseWork(DatabaseConnection* aConnection);
|
|
|
|
nsresult
|
|
DoIndexDatabaseWork(DatabaseConnection* aConnection);
|
|
|
|
nsresult
|
|
DoIndexKeyDatabaseWork(DatabaseConnection* aConnection);
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
nsresult
|
|
SendSuccessResult() override;
|
|
};
|
|
|
|
class Cursor::ContinueOp final
|
|
: public Cursor::CursorOpBase
|
|
{
|
|
friend class Cursor;
|
|
|
|
const CursorRequestParams mParams;
|
|
|
|
private:
|
|
// Only created by Cursor.
|
|
ContinueOp(Cursor* aCursor,
|
|
const CursorRequestParams& aParams)
|
|
: CursorOpBase(aCursor)
|
|
, mParams(aParams)
|
|
{
|
|
MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None);
|
|
}
|
|
|
|
// Reference counted.
|
|
~ContinueOp() override = default;
|
|
|
|
nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
nsresult
|
|
SendSuccessResult() override;
|
|
};
|
|
|
|
class Utils final
|
|
: public PBackgroundIndexedDBUtilsParent
|
|
{
|
|
#ifdef DEBUG
|
|
bool mActorDestroyed;
|
|
#endif
|
|
|
|
public:
|
|
Utils();
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Utils)
|
|
|
|
private:
|
|
// Reference counted.
|
|
~Utils() override;
|
|
|
|
// IPDL methods are only called by IPDL.
|
|
void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvDeleteMe() override;
|
|
|
|
mozilla::ipc::IPCResult
|
|
RecvGetFileReferences(const PersistenceType& aPersistenceType,
|
|
const nsCString& aOrigin,
|
|
const nsString& aDatabaseName,
|
|
const int64_t& aFileId,
|
|
int32_t* aRefCnt,
|
|
int32_t* aDBRefCnt,
|
|
int32_t* aSliceRefCnt,
|
|
bool* aResult) override;
|
|
};
|
|
|
|
class GetFileReferencesHelper final
|
|
: public Runnable
|
|
{
|
|
PersistenceType mPersistenceType;
|
|
nsCString mOrigin;
|
|
nsString mDatabaseName;
|
|
int64_t mFileId;
|
|
|
|
mozilla::Mutex mMutex;
|
|
mozilla::CondVar mCondVar;
|
|
int32_t mMemRefCnt;
|
|
int32_t mDBRefCnt;
|
|
int32_t mSliceRefCnt;
|
|
bool mResult;
|
|
bool mWaiting;
|
|
|
|
public:
|
|
GetFileReferencesHelper(PersistenceType aPersistenceType,
|
|
const nsACString& aOrigin,
|
|
const nsAString& aDatabaseName,
|
|
int64_t aFileId)
|
|
: mPersistenceType(aPersistenceType)
|
|
, mOrigin(aOrigin)
|
|
, mDatabaseName(aDatabaseName)
|
|
, mFileId(aFileId)
|
|
, mMutex("GetFileReferencesHelper::mMutex")
|
|
, mCondVar(mMutex, "GetFileReferencesHelper::mCondVar")
|
|
, mMemRefCnt(-1)
|
|
, mDBRefCnt(-1)
|
|
, mSliceRefCnt(-1)
|
|
, mResult(false)
|
|
, mWaiting(true)
|
|
{ }
|
|
|
|
nsresult
|
|
DispatchAndReturnFileReferences(int32_t* aMemRefCnt,
|
|
int32_t* aDBRefCnt,
|
|
int32_t* aSliceRefCnt,
|
|
bool* aResult);
|
|
|
|
private:
|
|
~GetFileReferencesHelper() override = default;
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
};
|
|
|
|
class FlushPendingFileDeletionsRunnable final
|
|
: public Runnable
|
|
{
|
|
private:
|
|
~FlushPendingFileDeletionsRunnable() override = default;
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
};
|
|
|
|
class PermissionRequestHelper final
|
|
: public PermissionRequestBase
|
|
, public PIndexedDBPermissionRequestParent
|
|
{
|
|
bool mActorDestroyed;
|
|
|
|
public:
|
|
PermissionRequestHelper(Element* aOwnerElement,
|
|
nsIPrincipal* aPrincipal)
|
|
: PermissionRequestBase(aOwnerElement, aPrincipal)
|
|
, mActorDestroyed(false)
|
|
{ }
|
|
|
|
protected:
|
|
~PermissionRequestHelper() override = default;
|
|
|
|
private:
|
|
void
|
|
OnPromptComplete(PermissionValue aPermissionValue) override;
|
|
|
|
void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
};
|
|
|
|
/*******************************************************************************
|
|
* Other class declarations
|
|
******************************************************************************/
|
|
|
|
struct DatabaseActorInfo final
|
|
{
|
|
friend class nsAutoPtr<DatabaseActorInfo>;
|
|
|
|
RefPtr<FullDatabaseMetadata> mMetadata;
|
|
nsTArray<Database*> mLiveDatabases;
|
|
RefPtr<FactoryOp> mWaitingFactoryOp;
|
|
|
|
DatabaseActorInfo(FullDatabaseMetadata* aMetadata,
|
|
Database* aDatabase)
|
|
: mMetadata(aMetadata)
|
|
{
|
|
MOZ_ASSERT(aDatabase);
|
|
|
|
MOZ_COUNT_CTOR(DatabaseActorInfo);
|
|
|
|
mLiveDatabases.AppendElement(aDatabase);
|
|
}
|
|
|
|
private:
|
|
~DatabaseActorInfo()
|
|
{
|
|
MOZ_ASSERT(mLiveDatabases.IsEmpty());
|
|
MOZ_ASSERT(!mWaitingFactoryOp ||
|
|
!mWaitingFactoryOp->HasBlockedDatabases());
|
|
|
|
MOZ_COUNT_DTOR(DatabaseActorInfo);
|
|
}
|
|
};
|
|
|
|
class DatabaseLoggingInfo final
|
|
{
|
|
#ifdef DEBUG
|
|
// Just for potential warnings.
|
|
friend class Factory;
|
|
#endif
|
|
|
|
LoggingInfo mLoggingInfo;
|
|
|
|
public:
|
|
explicit
|
|
DatabaseLoggingInfo(const LoggingInfo& aLoggingInfo)
|
|
: mLoggingInfo(aLoggingInfo)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aLoggingInfo.nextTransactionSerialNumber());
|
|
MOZ_ASSERT(aLoggingInfo.nextVersionChangeTransactionSerialNumber());
|
|
MOZ_ASSERT(aLoggingInfo.nextRequestSerialNumber());
|
|
}
|
|
|
|
const nsID&
|
|
Id() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return mLoggingInfo.backgroundChildLoggingId();
|
|
}
|
|
|
|
int64_t
|
|
NextTransactionSN(IDBTransaction::Mode aMode)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mLoggingInfo.nextTransactionSerialNumber() < INT64_MAX);
|
|
MOZ_ASSERT(mLoggingInfo.nextVersionChangeTransactionSerialNumber() >
|
|
INT64_MIN);
|
|
|
|
if (aMode == IDBTransaction::VERSION_CHANGE) {
|
|
return mLoggingInfo.nextVersionChangeTransactionSerialNumber()--;
|
|
}
|
|
|
|
return mLoggingInfo.nextTransactionSerialNumber()++;
|
|
}
|
|
|
|
uint64_t
|
|
NextRequestSN()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mLoggingInfo.nextRequestSerialNumber() < UINT64_MAX);
|
|
|
|
return mLoggingInfo.nextRequestSerialNumber()++;
|
|
}
|
|
|
|
NS_INLINE_DECL_REFCOUNTING(DatabaseLoggingInfo)
|
|
|
|
private:
|
|
~DatabaseLoggingInfo();
|
|
};
|
|
|
|
class BlobImplStoredFile final
|
|
: public BlobImplFile
|
|
{
|
|
RefPtr<FileInfo> mFileInfo;
|
|
const bool mSnapshot;
|
|
|
|
public:
|
|
BlobImplStoredFile(nsIFile* aFile, FileInfo* aFileInfo, bool aSnapshot)
|
|
: BlobImplFile(aFile)
|
|
, mFileInfo(aFileInfo)
|
|
, mSnapshot(aSnapshot)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
// Getting the content type is not currently supported off the main thread.
|
|
// This isn't a problem here because:
|
|
//
|
|
// 1. The real content type is stored in the structured clone data and
|
|
// that's all that the DOM will see. This blob's data will be updated
|
|
// during RecvSetMysteryBlobInfo().
|
|
// 2. The nsExternalHelperAppService guesses the content type based only
|
|
// on the file extension. Our stored files have no extension so the
|
|
// current code path fails and sets the content type to the empty
|
|
// string.
|
|
//
|
|
// So, this is a hack to keep the nsExternalHelperAppService out of the
|
|
// picture entirely. Eventually we should probably fix this some other way.
|
|
mContentType.Truncate();
|
|
mIsFile = false;
|
|
}
|
|
|
|
bool
|
|
IsShareable(FileManager* aFileManager) const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return mFileInfo->Manager() == aFileManager && !mSnapshot;
|
|
}
|
|
|
|
FileInfo*
|
|
GetFileInfo() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return mFileInfo;
|
|
}
|
|
|
|
private:
|
|
~BlobImplStoredFile() override = default;
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
NS_DECLARE_STATIC_IID_ACCESSOR(BLOB_IMPL_STORED_FILE_IID)
|
|
|
|
int64_t
|
|
GetFileId() override
|
|
{
|
|
MOZ_ASSERT(mFileInfo);
|
|
|
|
return mFileInfo->Id();
|
|
}
|
|
};
|
|
|
|
NS_DEFINE_STATIC_IID_ACCESSOR(BlobImplStoredFile, BLOB_IMPL_STORED_FILE_IID)
|
|
|
|
class QuotaClient final
|
|
: public mozilla::dom::quota::Client
|
|
{
|
|
static QuotaClient* sInstance;
|
|
|
|
nsCOMPtr<nsIEventTarget> mBackgroundThread;
|
|
nsTArray<RefPtr<Maintenance>> mMaintenanceQueue;
|
|
RefPtr<Maintenance> mCurrentMaintenance;
|
|
RefPtr<nsThreadPool> mMaintenanceThreadPool;
|
|
bool mShutdownRequested;
|
|
|
|
public:
|
|
QuotaClient();
|
|
|
|
static QuotaClient*
|
|
GetInstance()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return sInstance;
|
|
}
|
|
|
|
static bool
|
|
IsShuttingDownOnBackgroundThread()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (sInstance) {
|
|
return sInstance->IsShuttingDown();
|
|
}
|
|
|
|
return QuotaManager::IsShuttingDown();
|
|
}
|
|
|
|
static bool
|
|
IsShuttingDownOnNonBackgroundThread()
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
return QuotaManager::IsShuttingDown();
|
|
}
|
|
|
|
nsIEventTarget*
|
|
BackgroundThread() const
|
|
{
|
|
MOZ_ASSERT(mBackgroundThread);
|
|
return mBackgroundThread;
|
|
}
|
|
|
|
bool
|
|
IsShuttingDown() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return mShutdownRequested;
|
|
}
|
|
|
|
already_AddRefed<Maintenance>
|
|
GetCurrentMaintenance() const
|
|
{
|
|
RefPtr<Maintenance> result = mCurrentMaintenance;
|
|
return result.forget();
|
|
}
|
|
|
|
void
|
|
NoteFinishedMaintenance(Maintenance* aMaintenance)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aMaintenance);
|
|
MOZ_ASSERT(mCurrentMaintenance == aMaintenance);
|
|
|
|
mCurrentMaintenance = nullptr;
|
|
ProcessMaintenanceQueue();
|
|
}
|
|
|
|
nsThreadPool*
|
|
GetOrCreateThreadPool();
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(QuotaClient, override)
|
|
|
|
mozilla::dom::quota::Client::Type
|
|
GetType() override;
|
|
|
|
nsresult
|
|
InitOrigin(PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
UsageInfo* aUsageInfo) override;
|
|
|
|
nsresult
|
|
GetUsageForOrigin(PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
UsageInfo* aUsageInfo) override;
|
|
|
|
void
|
|
OnOriginClearCompleted(PersistenceType aPersistenceType,
|
|
const nsACString& aOrigin)
|
|
override;
|
|
|
|
void
|
|
ReleaseIOThreadObjects() override;
|
|
|
|
void
|
|
AbortOperations(const nsACString& aOrigin) override;
|
|
|
|
void
|
|
AbortOperationsForProcess(ContentParentId aContentParentId) override;
|
|
|
|
void
|
|
StartIdleMaintenance() override;
|
|
|
|
void
|
|
StopIdleMaintenance() override;
|
|
|
|
void
|
|
ShutdownWorkThreads() override;
|
|
|
|
void
|
|
DidInitialize(QuotaManager* aQuotaManager) override;
|
|
|
|
void
|
|
WillShutdown() override;
|
|
|
|
private:
|
|
~QuotaClient() override;
|
|
|
|
nsresult
|
|
GetDirectory(PersistenceType aPersistenceType,
|
|
const nsACString& aOrigin,
|
|
nsIFile** aDirectory);
|
|
|
|
nsresult
|
|
GetUsageForDirectoryInternal(nsIFile* aDirectory,
|
|
UsageInfo* aUsageInfo,
|
|
bool aDatabaseFiles);
|
|
|
|
// Runs on the PBackground thread. Checks to see if there's a queued
|
|
// Maintenance to run.
|
|
void
|
|
ProcessMaintenanceQueue();
|
|
};
|
|
|
|
class Maintenance final
|
|
: public Runnable
|
|
, public OpenDirectoryListener
|
|
{
|
|
struct DirectoryInfo;
|
|
|
|
enum class State
|
|
{
|
|
// Newly created on the PBackground thread. Will proceed immediately or be
|
|
// added to the maintenance queue. The next step is either
|
|
// DirectoryOpenPending if IndexedDatabaseManager is running, or
|
|
// CreateIndexedDatabaseManager if not.
|
|
Initial = 0,
|
|
|
|
// Create IndexedDatabaseManager on the main thread. The next step is either
|
|
// Finishing if IndexedDatabaseManager initialization fails, or
|
|
// IndexedDatabaseManagerOpen if initialization succeeds.
|
|
CreateIndexedDatabaseManager,
|
|
|
|
// Call OpenDirectory() on the PBackground thread. The next step is
|
|
// DirectoryOpenPending.
|
|
IndexedDatabaseManagerOpen,
|
|
|
|
// Waiting for directory open allowed on the PBackground thread. The next
|
|
// step is either Finishing if directory lock failed to acquire, or
|
|
// DirectoryWorkOpen if directory lock is acquired.
|
|
DirectoryOpenPending,
|
|
|
|
// Waiting to do/doing work on the QuotaManager IO thread. The next step is
|
|
// BeginDatabaseMaintenance.
|
|
DirectoryWorkOpen,
|
|
|
|
// Dispatching a runnable for each database on the PBackground thread. The
|
|
// next state is either WaitingForDatabaseMaintenancesToComplete if at least
|
|
// one runnable has been dispatched, or Finishing otherwise.
|
|
BeginDatabaseMaintenance,
|
|
|
|
// Waiting for DatabaseMaintenance to finish on maintenance thread pool.
|
|
// The next state is Finishing if the last runnable has finished.
|
|
WaitingForDatabaseMaintenancesToComplete,
|
|
|
|
// Waiting to finish/finishing on the PBackground thread. The next step is
|
|
// Completed.
|
|
Finishing,
|
|
|
|
// All done.
|
|
Complete
|
|
};
|
|
|
|
RefPtr<QuotaClient> mQuotaClient;
|
|
PRTime mStartTime;
|
|
RefPtr<DirectoryLock> mDirectoryLock;
|
|
nsTArray<DirectoryInfo> mDirectoryInfos;
|
|
nsDataHashtable<nsStringHashKey, DatabaseMaintenance*> mDatabaseMaintenances;
|
|
Atomic<bool> mAborted;
|
|
State mState;
|
|
|
|
public:
|
|
explicit Maintenance(QuotaClient* aQuotaClient)
|
|
: mQuotaClient(aQuotaClient)
|
|
, mStartTime(PR_Now())
|
|
, mAborted(false)
|
|
, mState(State::Initial)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aQuotaClient);
|
|
MOZ_ASSERT(QuotaClient::GetInstance() == aQuotaClient);
|
|
MOZ_ASSERT(mStartTime);
|
|
}
|
|
|
|
nsIEventTarget*
|
|
BackgroundThread() const
|
|
{
|
|
MOZ_ASSERT(mQuotaClient);
|
|
return mQuotaClient->BackgroundThread();
|
|
}
|
|
|
|
PRTime
|
|
StartTime() const
|
|
{
|
|
return mStartTime;
|
|
}
|
|
|
|
bool
|
|
IsAborted() const
|
|
{
|
|
return mAborted;
|
|
}
|
|
|
|
void
|
|
RunImmediately()
|
|
{
|
|
MOZ_ASSERT(mState == State::Initial);
|
|
|
|
Unused << this->Run();
|
|
}
|
|
|
|
void
|
|
Abort()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
mAborted = true;
|
|
}
|
|
|
|
void
|
|
RegisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance);
|
|
|
|
void
|
|
UnregisterDatabaseMaintenance(DatabaseMaintenance* aDatabaseMaintenance);
|
|
|
|
already_AddRefed<DatabaseMaintenance>
|
|
GetDatabaseMaintenance(const nsAString& aDatabasePath) const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
RefPtr<DatabaseMaintenance> result =
|
|
mDatabaseMaintenances.Get(aDatabasePath);
|
|
return result.forget();
|
|
}
|
|
|
|
private:
|
|
~Maintenance() override
|
|
{
|
|
MOZ_ASSERT(mState == State::Complete);
|
|
MOZ_ASSERT(!mDatabaseMaintenances.Count());
|
|
}
|
|
|
|
// Runs on the PBackground thread. Checks if IndexedDatabaseManager is
|
|
// running. Calls OpenDirectory() or dispatches to the main thread on which
|
|
// CreateIndexedDatabaseManager() is called.
|
|
nsresult
|
|
Start();
|
|
|
|
// Runs on the main thread. Once IndexedDatabaseManager is created it will
|
|
// dispatch to the PBackground thread on which OpenDirectory() is called.
|
|
nsresult
|
|
CreateIndexedDatabaseManager();
|
|
|
|
// Runs on the PBackground thread. Once QuotaManager has given a lock it will
|
|
// call DirectoryOpen().
|
|
nsresult
|
|
OpenDirectory();
|
|
|
|
// Runs on the PBackground thread. Dispatches to the QuotaManager I/O thread.
|
|
nsresult
|
|
DirectoryOpen();
|
|
|
|
// Runs on the QuotaManager I/O thread. Once it finds databases it will
|
|
// dispatch to the PBackground thread on which BeginDatabaseMaintenance()
|
|
// is called.
|
|
nsresult
|
|
DirectoryWork();
|
|
|
|
// Runs on the PBackground thread. It dispatches a runnable for each database.
|
|
nsresult
|
|
BeginDatabaseMaintenance();
|
|
|
|
// Runs on the PBackground thread. Called when the maintenance is finished or
|
|
// if any of above methods fails.
|
|
void
|
|
Finish();
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
|
|
// OpenDirectoryListener overrides.
|
|
void
|
|
DirectoryLockAcquired(DirectoryLock* aLock) override;
|
|
|
|
void
|
|
DirectoryLockFailed() override;
|
|
};
|
|
|
|
struct Maintenance::DirectoryInfo final
|
|
{
|
|
const nsCString mGroup;
|
|
const nsCString mOrigin;
|
|
nsTArray<nsString> mDatabasePaths;
|
|
const PersistenceType mPersistenceType;
|
|
|
|
DirectoryInfo(PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
nsTArray<nsString>&& aDatabasePaths)
|
|
: mGroup(aGroup)
|
|
, mOrigin(aOrigin)
|
|
, mDatabasePaths(Move(aDatabasePaths))
|
|
, mPersistenceType(aPersistenceType)
|
|
{
|
|
MOZ_ASSERT(aPersistenceType != PERSISTENCE_TYPE_INVALID);
|
|
MOZ_ASSERT(!aGroup.IsEmpty());
|
|
MOZ_ASSERT(!aOrigin.IsEmpty());
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(!mDatabasePaths.IsEmpty());
|
|
for (const nsString& databasePath : mDatabasePaths) {
|
|
MOZ_ASSERT(!databasePath.IsEmpty());
|
|
}
|
|
#endif
|
|
|
|
MOZ_COUNT_CTOR(Maintenance::DirectoryInfo);
|
|
}
|
|
|
|
DirectoryInfo(DirectoryInfo&& aOther)
|
|
: mGroup(Move(aOther.mGroup))
|
|
, mOrigin(Move(aOther.mOrigin))
|
|
, mDatabasePaths(Move(aOther.mDatabasePaths))
|
|
, mPersistenceType(Move(aOther.mPersistenceType))
|
|
{
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(!mDatabasePaths.IsEmpty());
|
|
for (const nsString& databasePath : mDatabasePaths) {
|
|
MOZ_ASSERT(!databasePath.IsEmpty());
|
|
}
|
|
#endif
|
|
|
|
MOZ_COUNT_CTOR(Maintenance::DirectoryInfo);
|
|
}
|
|
|
|
~DirectoryInfo()
|
|
{
|
|
MOZ_COUNT_DTOR(Maintenance::DirectoryInfo);
|
|
}
|
|
|
|
DirectoryInfo(const DirectoryInfo& aOther) = delete;
|
|
};
|
|
|
|
class DatabaseMaintenance final
|
|
: public Runnable
|
|
{
|
|
// The minimum amount of time that has passed since the last vacuum before we
|
|
// will attempt to analyze the database for fragmentation.
|
|
static const PRTime kMinVacuumAge =
|
|
PRTime(PR_USEC_PER_SEC) * 60 * 60 * 24 * 7;
|
|
|
|
// If the percent of database pages that are not in contiguous order is higher
|
|
// than this percentage we will attempt a vacuum.
|
|
static const int32_t kPercentUnorderedThreshold = 30;
|
|
|
|
// If the percent of file size growth since the last vacuum is higher than
|
|
// this percentage we will attempt a vacuum.
|
|
static const int32_t kPercentFileSizeGrowthThreshold = 10;
|
|
|
|
// The number of freelist pages beyond which we will favor an incremental
|
|
// vacuum over a full vacuum.
|
|
static const int32_t kMaxFreelistThreshold = 5;
|
|
|
|
// If the percent of unused file bytes in the database exceeds this percentage
|
|
// then we will attempt a full vacuum.
|
|
static const int32_t kPercentUnusedThreshold = 20;
|
|
|
|
class AutoProgressHandler;
|
|
|
|
enum class MaintenanceAction
|
|
{
|
|
Nothing = 0,
|
|
IncrementalVacuum,
|
|
FullVacuum
|
|
};
|
|
|
|
RefPtr<Maintenance> mMaintenance;
|
|
const nsCString mGroup;
|
|
const nsCString mOrigin;
|
|
const nsString mDatabasePath;
|
|
nsCOMPtr<nsIRunnable> mCompleteCallback;
|
|
const PersistenceType mPersistenceType;
|
|
|
|
public:
|
|
DatabaseMaintenance(Maintenance* aMaintenance,
|
|
PersistenceType aPersistenceType,
|
|
const nsCString& aGroup,
|
|
const nsCString& aOrigin,
|
|
const nsString& aDatabasePath)
|
|
: mMaintenance(aMaintenance)
|
|
, mGroup(aGroup)
|
|
, mOrigin(aOrigin)
|
|
, mDatabasePath(aDatabasePath)
|
|
, mPersistenceType(aPersistenceType)
|
|
{ }
|
|
|
|
const nsString&
|
|
DatabasePath() const
|
|
{
|
|
return mDatabasePath;
|
|
}
|
|
|
|
void
|
|
WaitForCompletion(nsIRunnable* aCallback)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mCompleteCallback);
|
|
|
|
mCompleteCallback = aCallback;
|
|
}
|
|
|
|
private:
|
|
~DatabaseMaintenance() override = default;
|
|
|
|
// Runs on maintenance thread pool. Does maintenance on the database.
|
|
void
|
|
PerformMaintenanceOnDatabase();
|
|
|
|
// Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
|
|
nsresult
|
|
CheckIntegrity(mozIStorageConnection* aConnection, bool* aOk);
|
|
|
|
// Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
|
|
nsresult
|
|
DetermineMaintenanceAction(mozIStorageConnection* aConnection,
|
|
nsIFile* aDatabaseFile,
|
|
MaintenanceAction* aMaintenanceAction);
|
|
|
|
// Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
|
|
void
|
|
IncrementalVacuum(mozIStorageConnection* aConnection);
|
|
|
|
// Runs on maintenance thread pool as part of PerformMaintenanceOnDatabase.
|
|
void
|
|
FullVacuum(mozIStorageConnection* aConnection,
|
|
nsIFile* aDatabaseFile);
|
|
|
|
// Runs on the PBackground thread. It dispatches a complete callback and
|
|
// unregisters from Maintenance.
|
|
void
|
|
RunOnOwningThread();
|
|
|
|
// Runs on maintenance thread pool. Once it performs database maintenance
|
|
// it will dispatch to the PBackground thread on which RunOnOwningThread()
|
|
// is called.
|
|
void
|
|
RunOnConnectionThread();
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
};
|
|
|
|
class MOZ_STACK_CLASS DatabaseMaintenance::AutoProgressHandler final
|
|
: public mozIStorageProgressHandler
|
|
{
|
|
Maintenance* mMaintenance;
|
|
mozIStorageConnection* mConnection;
|
|
|
|
NS_DECL_OWNINGTHREAD
|
|
|
|
#ifdef DEBUG
|
|
// This class is stack-based so we never actually allow AddRef/Release to do
|
|
// anything. But we need to know if any consumer *thinks* that they have a
|
|
// reference to this object so we track the reference countin DEBUG builds.
|
|
nsrefcnt mDEBUGRefCnt;
|
|
#endif
|
|
|
|
public:
|
|
explicit AutoProgressHandler(Maintenance* aMaintenance)
|
|
: mMaintenance(aMaintenance)
|
|
, mConnection(nullptr)
|
|
#ifdef DEBUG
|
|
, mDEBUGRefCnt(0)
|
|
#endif
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
|
|
MOZ_ASSERT(aMaintenance);
|
|
}
|
|
|
|
~AutoProgressHandler()
|
|
{
|
|
NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
|
|
|
|
if (mConnection) {
|
|
Unregister();
|
|
}
|
|
|
|
MOZ_ASSERT(!mDEBUGRefCnt);
|
|
}
|
|
|
|
nsresult
|
|
Register(mozIStorageConnection* aConnection);
|
|
|
|
// We don't want the mRefCnt member but this class does not "inherit"
|
|
// nsISupports.
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
private:
|
|
void
|
|
Unregister();
|
|
|
|
NS_DECL_MOZISTORAGEPROGRESSHANDLER
|
|
|
|
// Not available for the heap!
|
|
void*
|
|
operator new(size_t) = delete;
|
|
void*
|
|
operator new[](size_t) = delete;
|
|
void
|
|
operator delete(void*) = delete;
|
|
void
|
|
operator delete[](void*) = delete;
|
|
};
|
|
|
|
class IntString : public nsAutoString
|
|
{
|
|
public:
|
|
explicit
|
|
IntString(int64_t aInteger)
|
|
{
|
|
AppendInt(aInteger);
|
|
}
|
|
};
|
|
|
|
#ifdef DEBUG
|
|
|
|
class DEBUGThreadSlower final
|
|
: public nsIThreadObserver
|
|
{
|
|
public:
|
|
DEBUGThreadSlower()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(kDEBUGThreadSleepMS);
|
|
}
|
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
private:
|
|
~DEBUGThreadSlower()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
}
|
|
|
|
NS_DECL_NSITHREADOBSERVER
|
|
};
|
|
|
|
#endif // DEBUG
|
|
|
|
/*******************************************************************************
|
|
* Helper classes
|
|
******************************************************************************/
|
|
|
|
class MOZ_STACK_CLASS FileHelper final
|
|
{
|
|
RefPtr<FileManager> mFileManager;
|
|
|
|
nsCOMPtr<nsIFile> mFileDirectory;
|
|
nsCOMPtr<nsIFile> mJournalDirectory;
|
|
|
|
public:
|
|
explicit FileHelper(FileManager* aFileManager)
|
|
: mFileManager(aFileManager)
|
|
{ }
|
|
|
|
nsresult
|
|
Init();
|
|
|
|
already_AddRefed<nsIFile>
|
|
GetFile(FileInfo* aFileInfo);
|
|
|
|
already_AddRefed<nsIFile>
|
|
GetCheckedFile(FileInfo* aFileInfo);
|
|
|
|
already_AddRefed<nsIFile>
|
|
GetJournalFile(FileInfo* aFileInfo);
|
|
|
|
nsresult
|
|
CreateFileFromStream(nsIFile* aFile,
|
|
nsIFile* aJournalFile,
|
|
nsIInputStream* aInputStream,
|
|
bool aCompress);
|
|
|
|
nsresult
|
|
ReplaceFile(nsIFile* aFile,
|
|
nsIFile* aNewFile,
|
|
nsIFile* aNewJournalFile);
|
|
|
|
nsresult
|
|
RemoveFile(nsIFile* aFile,
|
|
nsIFile* aJournalFile);
|
|
|
|
already_AddRefed<FileInfo>
|
|
GetNewFileInfo();
|
|
|
|
private:
|
|
nsresult
|
|
SyncCopy(nsIInputStream* aInputStream,
|
|
nsIOutputStream* aOutputStream,
|
|
char* aBuffer,
|
|
uint32_t aBufferSize);
|
|
};
|
|
|
|
/*******************************************************************************
|
|
* Helper Functions
|
|
******************************************************************************/
|
|
|
|
bool
|
|
TokenizerIgnoreNothing(char16_t /* aChar */)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
nsresult
|
|
DeserializeStructuredCloneFile(FileManager* aFileManager,
|
|
const nsString& aText,
|
|
StructuredCloneFile* aFile)
|
|
{
|
|
MOZ_ASSERT(!aText.IsEmpty());
|
|
MOZ_ASSERT(aFile);
|
|
|
|
StructuredCloneFile::FileType type;
|
|
|
|
switch (aText.First()) {
|
|
case char16_t('-'):
|
|
type = StructuredCloneFile::eMutableFile;
|
|
break;
|
|
|
|
case char16_t('.'):
|
|
type = StructuredCloneFile::eStructuredClone;
|
|
break;
|
|
|
|
case char16_t('/'):
|
|
type = StructuredCloneFile::eWasmBytecode;
|
|
break;
|
|
|
|
case char16_t('\\'):
|
|
type = StructuredCloneFile::eWasmCompiled;
|
|
break;
|
|
|
|
default:
|
|
type = StructuredCloneFile::eBlob;
|
|
}
|
|
|
|
nsresult rv;
|
|
int32_t id;
|
|
|
|
if (type == StructuredCloneFile::eBlob) {
|
|
id = aText.ToInteger(&rv);
|
|
} else {
|
|
nsString text(Substring(aText, 1));
|
|
|
|
id = text.ToInteger(&rv);
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
RefPtr<FileInfo> fileInfo = aFileManager->GetFileInfo(id);
|
|
MOZ_ASSERT(fileInfo);
|
|
|
|
aFile->mFileInfo.swap(fileInfo);
|
|
aFile->mType = type;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CheckWasmModule(FileHelper* aFileHelper,
|
|
StructuredCloneFile* aBytecodeFile,
|
|
StructuredCloneFile* aCompiledFile)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aFileHelper);
|
|
MOZ_ASSERT(aBytecodeFile);
|
|
MOZ_ASSERT(aCompiledFile);
|
|
MOZ_ASSERT(aBytecodeFile->mType == StructuredCloneFile::eWasmBytecode);
|
|
MOZ_ASSERT(aCompiledFile->mType == StructuredCloneFile::eWasmCompiled);
|
|
|
|
nsCOMPtr<nsIFile> compiledFile =
|
|
aFileHelper->GetCheckedFile(aCompiledFile->mFileInfo);
|
|
if (NS_WARN_IF(!compiledFile)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult rv;
|
|
|
|
bool match;
|
|
{
|
|
ScopedPRFileDesc compiledFileDesc;
|
|
rv = compiledFile->OpenNSPRFileDesc(PR_RDONLY,
|
|
0644,
|
|
&compiledFileDesc.rwget());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
JS::BuildIdCharVector buildId;
|
|
bool ok = GetBuildId(&buildId);
|
|
if (NS_WARN_IF(!ok)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
match = JS::CompiledWasmModuleAssumptionsMatch(compiledFileDesc,
|
|
Move(buildId));
|
|
}
|
|
if (match) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Re-compile the module. It would be preferable to do this in the child
|
|
// (content process) instead of here in the parent, but that would be way more
|
|
// complex and without significant memory allocation or security benefits.
|
|
// See the discussion starting from
|
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1312808#c9 for more details.
|
|
nsCOMPtr<nsIFile> bytecodeFile =
|
|
aFileHelper->GetCheckedFile(aBytecodeFile->mFileInfo);
|
|
if (NS_WARN_IF(!bytecodeFile)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
ScopedPRFileDesc bytecodeFileDesc;
|
|
rv = bytecodeFile->OpenNSPRFileDesc(PR_RDONLY,
|
|
0644,
|
|
&bytecodeFileDesc.rwget());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
JS::BuildIdCharVector buildId;
|
|
bool ok = GetBuildId(&buildId);
|
|
if (NS_WARN_IF(!ok)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
RefPtr<JS::WasmModule> module = JS::DeserializeWasmModule(bytecodeFileDesc,
|
|
nullptr,
|
|
Move(buildId),
|
|
nullptr,
|
|
0,
|
|
0);
|
|
if (NS_WARN_IF(!module)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
size_t compiledSize;
|
|
module->serializedSize(nullptr, &compiledSize);
|
|
|
|
UniquePtr<uint8_t[]> compiled(new (fallible) uint8_t[compiledSize]);
|
|
if (NS_WARN_IF(!compiled)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
module->serialize(nullptr, 0, compiled.get(), compiledSize);
|
|
|
|
nsCOMPtr<nsIInputStream> inputStream;
|
|
rv = NS_NewByteInputStream(getter_AddRefs(inputStream),
|
|
reinterpret_cast<const char*>(compiled.get()),
|
|
compiledSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
RefPtr<FileInfo> newFileInfo = aFileHelper->GetNewFileInfo();
|
|
|
|
nsCOMPtr<nsIFile> newFile = aFileHelper->GetFile(newFileInfo);
|
|
if (NS_WARN_IF(!newFile)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> newJournalFile =
|
|
aFileHelper->GetJournalFile(newFileInfo);
|
|
if (NS_WARN_IF(!newJournalFile)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
rv = aFileHelper->CreateFileFromStream(newFile,
|
|
newJournalFile,
|
|
inputStream,
|
|
/* aCompress */ false);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
nsresult rv2 = aFileHelper->RemoveFile(newFile, newJournalFile);
|
|
if (NS_WARN_IF(NS_FAILED(rv2))) {
|
|
return rv;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
rv = aFileHelper->ReplaceFile(compiledFile, newFile, newJournalFile);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
nsresult rv2 = aFileHelper->RemoveFile(newFile, newJournalFile);
|
|
if (NS_WARN_IF(NS_FAILED(rv2))) {
|
|
return rv;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DeserializeStructuredCloneFiles(FileManager* aFileManager,
|
|
const nsAString& aText,
|
|
nsTArray<StructuredCloneFile>& aResult,
|
|
bool* aHasPreprocessInfo)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
nsCharSeparatedTokenizerTemplate<TokenizerIgnoreNothing>
|
|
tokenizer(aText, ' ');
|
|
|
|
nsAutoString token;
|
|
nsresult rv;
|
|
Maybe<FileHelper> fileHelper;
|
|
|
|
while (tokenizer.hasMoreTokens()) {
|
|
token = tokenizer.nextToken();
|
|
MOZ_ASSERT(!token.IsEmpty());
|
|
|
|
StructuredCloneFile* file = aResult.AppendElement();
|
|
rv = DeserializeStructuredCloneFile(aFileManager, token, file);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!aHasPreprocessInfo) {
|
|
continue;
|
|
}
|
|
|
|
if (file->mType == StructuredCloneFile::eWasmBytecode) {
|
|
*aHasPreprocessInfo = true;
|
|
}
|
|
else if (file->mType == StructuredCloneFile::eWasmCompiled) {
|
|
if (fileHelper.isNothing()) {
|
|
fileHelper.emplace(aFileManager);
|
|
|
|
rv = fileHelper->Init();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(aResult.Length() > 1);
|
|
MOZ_ASSERT(aResult[aResult.Length() - 2].mType ==
|
|
StructuredCloneFile::eWasmBytecode);
|
|
|
|
StructuredCloneFile* previousFile = &aResult[aResult.Length() - 2];
|
|
|
|
rv = CheckWasmModule(fileHelper.ptr(), previousFile, file);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
*aHasPreprocessInfo = true;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
GetDatabaseBaseFilename(const nsAString& aFilename,
|
|
nsDependentSubstring& aDatabaseBaseFilename)
|
|
{
|
|
MOZ_ASSERT(!aFilename.IsEmpty());
|
|
MOZ_ASSERT(aDatabaseBaseFilename.IsEmpty());
|
|
|
|
NS_NAMED_LITERAL_STRING(sqlite, ".sqlite");
|
|
|
|
if (!StringEndsWith(aFilename, sqlite) ||
|
|
aFilename.Length() == sqlite.Length()) {
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(aFilename.Length() > sqlite.Length());
|
|
|
|
aDatabaseBaseFilename.Rebind(aFilename,
|
|
0,
|
|
aFilename.Length() - sqlite.Length());
|
|
return true;
|
|
}
|
|
|
|
nsresult
|
|
SerializeStructuredCloneFiles(
|
|
PBackgroundParent* aBackgroundActor,
|
|
Database* aDatabase,
|
|
const nsTArray<StructuredCloneFile>& aFiles,
|
|
bool aForPreprocess,
|
|
FallibleTArray<SerializedStructuredCloneFile>& aResult)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aBackgroundActor);
|
|
MOZ_ASSERT(aDatabase);
|
|
MOZ_ASSERT(aResult.IsEmpty());
|
|
|
|
if (aFiles.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
FileManager* fileManager = aDatabase->GetFileManager();
|
|
|
|
nsCOMPtr<nsIFile> directory = fileManager->GetCheckedDirectory();
|
|
if (NS_WARN_IF(!directory)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
const uint32_t count = aFiles.Length();
|
|
|
|
if (NS_WARN_IF(!aResult.SetCapacity(count, fallible))) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
for (uint32_t index = 0; index < count; index++) {
|
|
const StructuredCloneFile& file = aFiles[index];
|
|
|
|
if (aForPreprocess &&
|
|
file.mType != StructuredCloneFile::eWasmBytecode &&
|
|
file.mType != StructuredCloneFile::eWasmCompiled) {
|
|
continue;
|
|
}
|
|
|
|
const int64_t fileId = file.mFileInfo->Id();
|
|
MOZ_ASSERT(fileId > 0);
|
|
|
|
nsCOMPtr<nsIFile> nativeFile =
|
|
fileManager->GetCheckedFileForId(directory, fileId);
|
|
if (NS_WARN_IF(!nativeFile)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
switch (file.mType) {
|
|
case StructuredCloneFile::eBlob: {
|
|
RefPtr<BlobImpl> impl = new BlobImplStoredFile(nativeFile,
|
|
file.mFileInfo,
|
|
/* aSnapshot */ false);
|
|
|
|
PBlobParent* actor =
|
|
BackgroundParent::GetOrCreateActorForBlobImpl(aBackgroundActor, impl);
|
|
if (!actor) {
|
|
// This can only fail if the child has crashed.
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
SerializedStructuredCloneFile* file = aResult.AppendElement(fallible);
|
|
MOZ_ASSERT(file);
|
|
|
|
file->file() = actor;
|
|
file->type() = StructuredCloneFile::eBlob;
|
|
|
|
break;
|
|
}
|
|
|
|
case StructuredCloneFile::eMutableFile: {
|
|
if (aDatabase->IsFileHandleDisabled()) {
|
|
SerializedStructuredCloneFile* file = aResult.AppendElement(fallible);
|
|
MOZ_ASSERT(file);
|
|
|
|
file->file() = null_t();
|
|
file->type() = StructuredCloneFile::eMutableFile;
|
|
} else {
|
|
RefPtr<MutableFile> actor =
|
|
MutableFile::Create(nativeFile, aDatabase, file.mFileInfo);
|
|
if (!actor) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
// Transfer ownership to IPDL.
|
|
actor->SetActorAlive();
|
|
|
|
if (!aDatabase->SendPBackgroundMutableFileConstructor(actor,
|
|
EmptyString(),
|
|
EmptyString())) {
|
|
// This can only fail if the child has crashed.
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
SerializedStructuredCloneFile* file = aResult.AppendElement(fallible);
|
|
MOZ_ASSERT(file);
|
|
|
|
file->file() = actor;
|
|
file->type() = StructuredCloneFile::eMutableFile;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case StructuredCloneFile::eStructuredClone: {
|
|
SerializedStructuredCloneFile* file = aResult.AppendElement(fallible);
|
|
MOZ_ASSERT(file);
|
|
|
|
file->file() = null_t();
|
|
file->type() = StructuredCloneFile::eStructuredClone;
|
|
|
|
break;
|
|
}
|
|
|
|
case StructuredCloneFile::eWasmBytecode:
|
|
case StructuredCloneFile::eWasmCompiled: {
|
|
if (!aForPreprocess) {
|
|
SerializedStructuredCloneFile* serializedFile =
|
|
aResult.AppendElement(fallible);
|
|
MOZ_ASSERT(serializedFile);
|
|
|
|
serializedFile->file() = null_t();
|
|
serializedFile->type() = file.mType;
|
|
} else {
|
|
RefPtr<BlobImpl> impl = new BlobImplStoredFile(nativeFile,
|
|
file.mFileInfo,
|
|
/* aSnapshot */ false);
|
|
|
|
PBlobParent* actor =
|
|
BackgroundParent::GetOrCreateActorForBlobImpl(aBackgroundActor,
|
|
impl);
|
|
if (!actor) {
|
|
// This can only fail if the child has crashed.
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
SerializedStructuredCloneFile* serializedFile =
|
|
aResult.AppendElement(fallible);
|
|
MOZ_ASSERT(serializedFile);
|
|
|
|
serializedFile->file() = actor;
|
|
serializedFile->type() = file.mType;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<nsIFile>
|
|
GetFileForFileInfo(FileInfo* aFileInfo)
|
|
{
|
|
FileManager* fileManager = aFileInfo->Manager();
|
|
nsCOMPtr<nsIFile> directory = fileManager->GetDirectory();
|
|
if (NS_WARN_IF(!directory)) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> file = fileManager->GetFileForId(directory,
|
|
aFileInfo->Id());
|
|
if (NS_WARN_IF(!file)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return file.forget();
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Globals
|
|
******************************************************************************/
|
|
|
|
// Counts the number of "live" Factory, FactoryOp and Database instances.
|
|
uint64_t gBusyCount = 0;
|
|
|
|
typedef nsTArray<RefPtr<FactoryOp>> FactoryOpArray;
|
|
|
|
StaticAutoPtr<FactoryOpArray> gFactoryOps;
|
|
|
|
// Maps a database id to information about live database actors.
|
|
typedef nsClassHashtable<nsCStringHashKey, DatabaseActorInfo>
|
|
DatabaseActorHashtable;
|
|
|
|
StaticAutoPtr<DatabaseActorHashtable> gLiveDatabaseHashtable;
|
|
|
|
StaticRefPtr<ConnectionPool> gConnectionPool;
|
|
|
|
StaticRefPtr<FileHandleThreadPool> gFileHandleThreadPool;
|
|
|
|
typedef nsDataHashtable<nsIDHashKey, DatabaseLoggingInfo*>
|
|
DatabaseLoggingInfoHashtable;
|
|
|
|
StaticAutoPtr<DatabaseLoggingInfoHashtable> gLoggingInfoHashtable;
|
|
|
|
typedef nsDataHashtable<nsUint32HashKey, uint32_t> TelemetryIdHashtable;
|
|
|
|
StaticAutoPtr<TelemetryIdHashtable> gTelemetryIdHashtable;
|
|
|
|
// Protects all reads and writes to gTelemetryIdHashtable.
|
|
StaticAutoPtr<Mutex> gTelemetryIdMutex;
|
|
|
|
#ifdef DEBUG
|
|
|
|
StaticRefPtr<DEBUGThreadSlower> gDEBUGThreadSlower;
|
|
|
|
#endif // DEBUG
|
|
|
|
|
|
void
|
|
IncreaseBusyCount()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
// If this is the first instance then we need to do some initialization.
|
|
if (!gBusyCount) {
|
|
MOZ_ASSERT(!gFactoryOps);
|
|
gFactoryOps = new FactoryOpArray();
|
|
|
|
MOZ_ASSERT(!gLiveDatabaseHashtable);
|
|
gLiveDatabaseHashtable = new DatabaseActorHashtable();
|
|
|
|
MOZ_ASSERT(!gLoggingInfoHashtable);
|
|
gLoggingInfoHashtable = new DatabaseLoggingInfoHashtable();
|
|
|
|
#ifdef DEBUG
|
|
if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) {
|
|
NS_WARNING("PBackground thread debugging enabled, priority has been "
|
|
"modified!");
|
|
nsCOMPtr<nsISupportsPriority> thread =
|
|
do_QueryInterface(NS_GetCurrentThread());
|
|
MOZ_ASSERT(thread);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(thread->SetPriority(kDEBUGThreadPriority));
|
|
}
|
|
|
|
if (kDEBUGThreadSleepMS) {
|
|
NS_WARNING("PBackground thread debugging enabled, sleeping after every "
|
|
"event!");
|
|
nsCOMPtr<nsIThreadInternal> thread =
|
|
do_QueryInterface(NS_GetCurrentThread());
|
|
MOZ_ASSERT(thread);
|
|
|
|
gDEBUGThreadSlower = new DEBUGThreadSlower();
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(thread->AddObserver(gDEBUGThreadSlower));
|
|
}
|
|
#endif // DEBUG
|
|
}
|
|
|
|
gBusyCount++;
|
|
}
|
|
|
|
void
|
|
DecreaseBusyCount()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(gBusyCount);
|
|
|
|
// Clean up if there are no more instances.
|
|
if (--gBusyCount == 0) {
|
|
MOZ_ASSERT(gLoggingInfoHashtable);
|
|
gLoggingInfoHashtable = nullptr;
|
|
|
|
MOZ_ASSERT(gLiveDatabaseHashtable);
|
|
MOZ_ASSERT(!gLiveDatabaseHashtable->Count());
|
|
gLiveDatabaseHashtable = nullptr;
|
|
|
|
MOZ_ASSERT(gFactoryOps);
|
|
MOZ_ASSERT(gFactoryOps->IsEmpty());
|
|
gFactoryOps = nullptr;
|
|
|
|
#ifdef DEBUG
|
|
if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) {
|
|
nsCOMPtr<nsISupportsPriority> thread =
|
|
do_QueryInterface(NS_GetCurrentThread());
|
|
MOZ_ASSERT(thread);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
thread->SetPriority(nsISupportsPriority::PRIORITY_NORMAL));
|
|
}
|
|
|
|
if (kDEBUGThreadSleepMS) {
|
|
MOZ_ASSERT(gDEBUGThreadSlower);
|
|
|
|
nsCOMPtr<nsIThreadInternal> thread =
|
|
do_QueryInterface(NS_GetCurrentThread());
|
|
MOZ_ASSERT(thread);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(thread->RemoveObserver(gDEBUGThreadSlower));
|
|
|
|
gDEBUGThreadSlower = nullptr;
|
|
}
|
|
#endif // DEBUG
|
|
}
|
|
}
|
|
|
|
uint32_t
|
|
TelemetryIdForFile(nsIFile* aFile)
|
|
{
|
|
// May be called on any thread!
|
|
|
|
MOZ_ASSERT(aFile);
|
|
MOZ_ASSERT(gTelemetryIdMutex);
|
|
|
|
// The storage directory is structured like this:
|
|
//
|
|
// <profile>/storage/<persistence>/<origin>/idb/<filename>.sqlite
|
|
//
|
|
// For the purposes of this function we're only concerned with the
|
|
// <persistence>, <origin>, and <filename> pieces.
|
|
|
|
nsString filename;
|
|
MOZ_ALWAYS_SUCCEEDS(aFile->GetLeafName(filename));
|
|
|
|
// Make sure we were given a database file.
|
|
NS_NAMED_LITERAL_STRING(sqliteExtension, ".sqlite");
|
|
|
|
MOZ_ASSERT(StringEndsWith(filename, sqliteExtension));
|
|
|
|
filename.Truncate(filename.Length() - sqliteExtension.Length());
|
|
|
|
// Get the "idb" directory.
|
|
nsCOMPtr<nsIFile> idbDirectory;
|
|
MOZ_ALWAYS_SUCCEEDS(aFile->GetParent(getter_AddRefs(idbDirectory)));
|
|
|
|
DebugOnly<nsString> idbLeafName;
|
|
MOZ_ASSERT(NS_SUCCEEDED(idbDirectory->GetLeafName(idbLeafName)));
|
|
MOZ_ASSERT(static_cast<nsString&>(idbLeafName).EqualsLiteral("idb"));
|
|
|
|
// Get the <origin> directory.
|
|
nsCOMPtr<nsIFile> originDirectory;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
idbDirectory->GetParent(getter_AddRefs(originDirectory)));
|
|
|
|
nsString origin;
|
|
MOZ_ALWAYS_SUCCEEDS(originDirectory->GetLeafName(origin));
|
|
|
|
// Any databases in these directories are owned by the application and should
|
|
// not have their filenames masked. Hopefully they also appear in the
|
|
// Telemetry.cpp whitelist.
|
|
if (origin.EqualsLiteral("chrome") ||
|
|
origin.EqualsLiteral("moz-safe-about+home")) {
|
|
return 0;
|
|
}
|
|
|
|
// Get the <persistence> directory.
|
|
nsCOMPtr<nsIFile> persistenceDirectory;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
originDirectory->GetParent(getter_AddRefs(persistenceDirectory)));
|
|
|
|
nsString persistence;
|
|
MOZ_ALWAYS_SUCCEEDS(persistenceDirectory->GetLeafName(persistence));
|
|
|
|
NS_NAMED_LITERAL_STRING(separator, "*");
|
|
|
|
uint32_t hashValue = HashString(persistence + separator +
|
|
origin + separator +
|
|
filename);
|
|
|
|
MutexAutoLock lock(*gTelemetryIdMutex);
|
|
|
|
if (!gTelemetryIdHashtable) {
|
|
gTelemetryIdHashtable = new TelemetryIdHashtable();
|
|
}
|
|
|
|
uint32_t id;
|
|
if (!gTelemetryIdHashtable->Get(hashValue, &id)) {
|
|
static uint32_t sNextId = 1;
|
|
|
|
// We're locked, no need for atomics.
|
|
id = sNextId++;
|
|
|
|
gTelemetryIdHashtable->Put(hashValue, id);
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/*******************************************************************************
|
|
* Exported functions
|
|
******************************************************************************/
|
|
|
|
PBackgroundIDBFactoryParent*
|
|
AllocPBackgroundIDBFactoryParent(const LoggingInfo& aLoggingInfo)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (NS_WARN_IF(!aLoggingInfo.nextTransactionSerialNumber()) ||
|
|
NS_WARN_IF(!aLoggingInfo.nextVersionChangeTransactionSerialNumber()) ||
|
|
NS_WARN_IF(!aLoggingInfo.nextRequestSerialNumber())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<Factory> actor = Factory::Create(aLoggingInfo);
|
|
MOZ_ASSERT(actor);
|
|
|
|
return actor.forget().take();
|
|
}
|
|
|
|
bool
|
|
RecvPBackgroundIDBFactoryConstructor(PBackgroundIDBFactoryParent* aActor,
|
|
const LoggingInfo& /* aLoggingInfo */)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
DeallocPBackgroundIDBFactoryParent(PBackgroundIDBFactoryParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
RefPtr<Factory> actor = dont_AddRef(static_cast<Factory*>(aActor));
|
|
return true;
|
|
}
|
|
|
|
PBackgroundIndexedDBUtilsParent*
|
|
AllocPBackgroundIndexedDBUtilsParent()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
RefPtr<Utils> actor = new Utils();
|
|
|
|
return actor.forget().take();
|
|
}
|
|
|
|
bool
|
|
DeallocPBackgroundIndexedDBUtilsParent(PBackgroundIndexedDBUtilsParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
RefPtr<Utils> actor = dont_AddRef(static_cast<Utils*>(aActor));
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
RecvFlushPendingFileDeletions()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
RefPtr<FlushPendingFileDeletionsRunnable> runnable =
|
|
new FlushPendingFileDeletionsRunnable();
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable.forget()));
|
|
|
|
return true;
|
|
}
|
|
|
|
PIndexedDBPermissionRequestParent*
|
|
AllocPIndexedDBPermissionRequestParent(Element* aOwnerElement,
|
|
nsIPrincipal* aPrincipal)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<PermissionRequestHelper> actor =
|
|
new PermissionRequestHelper(aOwnerElement, aPrincipal);
|
|
return actor.forget().take();
|
|
}
|
|
|
|
bool
|
|
RecvPIndexedDBPermissionRequestConstructor(
|
|
PIndexedDBPermissionRequestParent* aActor)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aActor);
|
|
|
|
auto* actor = static_cast<PermissionRequestHelper*>(aActor);
|
|
|
|
PermissionRequestBase::PermissionValue permission;
|
|
nsresult rv = actor->PromptIfNeeded(&permission);
|
|
if (NS_FAILED(rv)) {
|
|
return false;
|
|
}
|
|
|
|
if (permission != PermissionRequestBase::kPermissionPrompt) {
|
|
Unused <<
|
|
PIndexedDBPermissionRequestParent::Send__delete__(actor, permission);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
DeallocPIndexedDBPermissionRequestParent(
|
|
PIndexedDBPermissionRequestParent* aActor)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aActor);
|
|
|
|
RefPtr<PermissionRequestHelper> actor =
|
|
dont_AddRef(static_cast<PermissionRequestHelper*>(aActor));
|
|
return true;
|
|
}
|
|
|
|
already_AddRefed<mozilla::dom::quota::Client>
|
|
CreateQuotaClient()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
RefPtr<QuotaClient> client = new QuotaClient();
|
|
return client.forget();
|
|
}
|
|
|
|
FileHandleThreadPool*
|
|
GetFileHandleThreadPool()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (!gFileHandleThreadPool) {
|
|
RefPtr<FileHandleThreadPool> fileHandleThreadPool =
|
|
FileHandleThreadPool::Create();
|
|
if (NS_WARN_IF(!fileHandleThreadPool)) {
|
|
return nullptr;
|
|
}
|
|
|
|
gFileHandleThreadPool = fileHandleThreadPool;
|
|
}
|
|
|
|
return gFileHandleThreadPool;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* DatabaseConnection implementation
|
|
******************************************************************************/
|
|
|
|
DatabaseConnection::DatabaseConnection(
|
|
mozIStorageConnection* aStorageConnection,
|
|
FileManager* aFileManager)
|
|
: mStorageConnection(aStorageConnection)
|
|
, mFileManager(aFileManager)
|
|
, mInReadTransaction(false)
|
|
, mInWriteTransaction(false)
|
|
#ifdef DEBUG
|
|
, mDEBUGSavepointCount(0)
|
|
, mDEBUGThread(PR_GetCurrentThread())
|
|
#endif
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aStorageConnection);
|
|
MOZ_ASSERT(aFileManager);
|
|
}
|
|
|
|
DatabaseConnection::~DatabaseConnection()
|
|
{
|
|
MOZ_ASSERT(!mStorageConnection);
|
|
MOZ_ASSERT(!mFileManager);
|
|
MOZ_ASSERT(!mCachedStatements.Count());
|
|
MOZ_ASSERT(!mUpdateRefcountFunction);
|
|
MOZ_ASSERT(!mInWriteTransaction);
|
|
MOZ_ASSERT(!mDEBUGSavepointCount);
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::Init()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(!mInReadTransaction);
|
|
MOZ_ASSERT(!mInWriteTransaction);
|
|
|
|
CachedStatement stmt;
|
|
nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN;"), &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mInReadTransaction = true;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::GetCachedStatement(const nsACString& aQuery,
|
|
CachedStatement* aCachedStatement)
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(!aQuery.IsEmpty());
|
|
MOZ_ASSERT(aCachedStatement);
|
|
MOZ_ASSERT(mStorageConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::GetCachedStatement",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
|
|
if (!mCachedStatements.Get(aQuery, getter_AddRefs(stmt))) {
|
|
nsresult rv =
|
|
mStorageConnection->CreateStatement(aQuery, getter_AddRefs(stmt));
|
|
if (NS_FAILED(rv)) {
|
|
#ifdef DEBUG
|
|
nsCString msg;
|
|
MOZ_ALWAYS_SUCCEEDS(mStorageConnection->GetLastErrorString(msg));
|
|
|
|
nsAutoCString error =
|
|
NS_LITERAL_CSTRING("The statement '") + aQuery +
|
|
NS_LITERAL_CSTRING("' failed to compile with the error message '") +
|
|
msg + NS_LITERAL_CSTRING("'.");
|
|
|
|
NS_WARNING(error.get());
|
|
#endif
|
|
return rv;
|
|
}
|
|
|
|
mCachedStatements.Put(aQuery, stmt);
|
|
}
|
|
|
|
aCachedStatement->Assign(this, stmt.forget());
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::BeginWriteTransaction()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mStorageConnection);
|
|
MOZ_ASSERT(mInReadTransaction);
|
|
MOZ_ASSERT(!mInWriteTransaction);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::BeginWriteTransaction",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// Release our read locks.
|
|
CachedStatement rollbackStmt;
|
|
nsresult rv =
|
|
GetCachedStatement(NS_LITERAL_CSTRING("ROLLBACK;"), &rollbackStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = rollbackStmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mInReadTransaction = false;
|
|
|
|
if (!mUpdateRefcountFunction) {
|
|
MOZ_ASSERT(mFileManager);
|
|
|
|
RefPtr<UpdateRefcountFunction> function =
|
|
new UpdateRefcountFunction(this, mFileManager);
|
|
|
|
rv =
|
|
mStorageConnection->CreateFunction(NS_LITERAL_CSTRING("update_refcount"),
|
|
/* aNumArguments */ 2,
|
|
function);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mUpdateRefcountFunction.swap(function);
|
|
}
|
|
|
|
CachedStatement beginStmt;
|
|
rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"), &beginStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = beginStmt->Execute();
|
|
if (rv == NS_ERROR_STORAGE_BUSY) {
|
|
NS_WARNING("Received NS_ERROR_STORAGE_BUSY when attempting to start write "
|
|
"transaction, retrying for up to 10 seconds");
|
|
|
|
// Another thread must be using the database. Wait up to 10 seconds for
|
|
// that to complete.
|
|
TimeStamp start = TimeStamp::NowLoRes();
|
|
|
|
while (true) {
|
|
PR_Sleep(PR_MillisecondsToInterval(100));
|
|
|
|
rv = beginStmt->Execute();
|
|
if (rv != NS_ERROR_STORAGE_BUSY ||
|
|
TimeStamp::NowLoRes() - start > TimeDuration::FromSeconds(10)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mInWriteTransaction = true;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::CommitWriteTransaction()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mStorageConnection);
|
|
MOZ_ASSERT(!mInReadTransaction);
|
|
MOZ_ASSERT(mInWriteTransaction);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::CommitWriteTransaction",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
CachedStatement stmt;
|
|
nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mInWriteTransaction = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::RollbackWriteTransaction()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(!mInReadTransaction);
|
|
MOZ_ASSERT(mStorageConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::RollbackWriteTransaction",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (!mInWriteTransaction) {
|
|
return;
|
|
}
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("ROLLBACK;"), &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
// This may fail if SQLite already rolled back the transaction so ignore any
|
|
// errors.
|
|
Unused << stmt->Execute();
|
|
|
|
mInWriteTransaction = false;
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::FinishWriteTransaction()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mStorageConnection);
|
|
MOZ_ASSERT(!mInReadTransaction);
|
|
MOZ_ASSERT(!mInWriteTransaction);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::FinishWriteTransaction",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (mUpdateRefcountFunction) {
|
|
mUpdateRefcountFunction->Reset();
|
|
}
|
|
|
|
CachedStatement stmt;
|
|
nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN;"), &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
mInReadTransaction = true;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::StartSavepoint()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mStorageConnection);
|
|
MOZ_ASSERT(mUpdateRefcountFunction);
|
|
MOZ_ASSERT(mInWriteTransaction);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::StartSavepoint",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
CachedStatement stmt;
|
|
nsresult rv = GetCachedStatement(NS_LITERAL_CSTRING(SAVEPOINT_CLAUSE), &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mUpdateRefcountFunction->StartSavepoint();
|
|
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(mDEBUGSavepointCount < UINT32_MAX);
|
|
mDEBUGSavepointCount++;
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::ReleaseSavepoint()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mStorageConnection);
|
|
MOZ_ASSERT(mUpdateRefcountFunction);
|
|
MOZ_ASSERT(mInWriteTransaction);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::ReleaseSavepoint",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
CachedStatement stmt;
|
|
nsresult rv = GetCachedStatement(
|
|
NS_LITERAL_CSTRING("RELEASE " SAVEPOINT_CLAUSE),
|
|
&stmt);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = stmt->Execute();
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mUpdateRefcountFunction->ReleaseSavepoint();
|
|
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(mDEBUGSavepointCount);
|
|
mDEBUGSavepointCount--;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::RollbackSavepoint()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mStorageConnection);
|
|
MOZ_ASSERT(mUpdateRefcountFunction);
|
|
MOZ_ASSERT(mInWriteTransaction);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::RollbackSavepoint",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(mDEBUGSavepointCount);
|
|
mDEBUGSavepointCount--;
|
|
#endif
|
|
|
|
mUpdateRefcountFunction->RollbackSavepoint();
|
|
|
|
CachedStatement stmt;
|
|
nsresult rv = GetCachedStatement(
|
|
NS_LITERAL_CSTRING("ROLLBACK TO " SAVEPOINT_CLAUSE),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// This may fail if SQLite already rolled back the savepoint so ignore any
|
|
// errors.
|
|
Unused << stmt->Execute();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::CheckpointInternal(CheckpointMode aMode)
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(!mInReadTransaction);
|
|
MOZ_ASSERT(!mInWriteTransaction);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::CheckpointInternal",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsAutoCString stmtString;
|
|
stmtString.AssignLiteral("PRAGMA wal_checkpoint(");
|
|
|
|
switch (aMode) {
|
|
case CheckpointMode::Full:
|
|
// Ensures that the database is completely checkpointed and flushed to
|
|
// disk.
|
|
stmtString.AppendLiteral("FULL");
|
|
break;
|
|
|
|
case CheckpointMode::Restart:
|
|
// Like Full, but also ensures that the next write will start overwriting
|
|
// the existing WAL file rather than letting the WAL file grow.
|
|
stmtString.AppendLiteral("RESTART");
|
|
break;
|
|
|
|
case CheckpointMode::Truncate:
|
|
// Like Restart but also truncates the existing WAL file.
|
|
stmtString.AppendLiteral("TRUNCATE");
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Unknown CheckpointMode!");
|
|
}
|
|
|
|
stmtString.AppendLiteral(");");
|
|
|
|
CachedStatement stmt;
|
|
nsresult rv = GetCachedStatement(stmtString, &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::DoIdleProcessing(bool aNeedsCheckpoint)
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mInReadTransaction);
|
|
MOZ_ASSERT(!mInWriteTransaction);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::DoIdleProcessing",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
DatabaseConnection::CachedStatement freelistStmt;
|
|
uint32_t freelistCount;
|
|
nsresult rv = GetFreelistCount(freelistStmt, &freelistCount);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
freelistCount = 0;
|
|
}
|
|
|
|
CachedStatement rollbackStmt;
|
|
CachedStatement beginStmt;
|
|
if (aNeedsCheckpoint || freelistCount) {
|
|
rv = GetCachedStatement(NS_LITERAL_CSTRING("ROLLBACK;"), &rollbackStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN;"), &beginStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
// Release the connection's normal transaction. It's possible that it could
|
|
// fail, but that isn't a problem here.
|
|
Unused << rollbackStmt->Execute();
|
|
|
|
mInReadTransaction = false;
|
|
}
|
|
|
|
bool freedSomePages = false;
|
|
|
|
if (freelistCount) {
|
|
rv = ReclaimFreePagesWhileIdle(freelistStmt,
|
|
rollbackStmt,
|
|
freelistCount,
|
|
aNeedsCheckpoint,
|
|
&freedSomePages);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
MOZ_ASSERT(!freedSomePages);
|
|
}
|
|
|
|
// Make sure we didn't leave a transaction running.
|
|
MOZ_ASSERT(!mInReadTransaction);
|
|
MOZ_ASSERT(!mInWriteTransaction);
|
|
}
|
|
|
|
// Truncate the WAL if we were asked to or if we managed to free some space.
|
|
if (aNeedsCheckpoint || freedSomePages) {
|
|
rv = CheckpointInternal(CheckpointMode::Truncate);
|
|
Unused << NS_WARN_IF(NS_FAILED(rv));
|
|
}
|
|
|
|
// Finally try to restart the read transaction if we rolled it back earlier.
|
|
if (beginStmt) {
|
|
rv = beginStmt->Execute();
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mInReadTransaction = true;
|
|
} else {
|
|
NS_WARNING("Falied to restart read transaction!");
|
|
}
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::ReclaimFreePagesWhileIdle(
|
|
CachedStatement& aFreelistStatement,
|
|
CachedStatement& aRollbackStatement,
|
|
uint32_t aFreelistCount,
|
|
bool aNeedsCheckpoint,
|
|
bool* aFreedSomePages)
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aFreelistStatement);
|
|
MOZ_ASSERT(aRollbackStatement);
|
|
MOZ_ASSERT(aFreelistCount);
|
|
MOZ_ASSERT(aFreedSomePages);
|
|
MOZ_ASSERT(!mInReadTransaction);
|
|
MOZ_ASSERT(!mInWriteTransaction);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::ReclaimFreePagesWhileIdle",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// Make sure we don't keep working if anything else needs this thread.
|
|
nsIThread* currentThread = NS_GetCurrentThread();
|
|
MOZ_ASSERT(currentThread);
|
|
|
|
if (NS_HasPendingEvents(currentThread)) {
|
|
*aFreedSomePages = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Only try to free 10% at a time so that we can bail out if this connection
|
|
// suddenly becomes active or if the thread is needed otherwise.
|
|
nsAutoCString stmtString;
|
|
stmtString.AssignLiteral("PRAGMA incremental_vacuum(");
|
|
stmtString.AppendInt(std::max(uint64_t(1), uint64_t(aFreelistCount / 10)));
|
|
stmtString.AppendLiteral(");");
|
|
|
|
// Make all the statements we'll need up front.
|
|
CachedStatement incrementalVacuumStmt;
|
|
nsresult rv = GetCachedStatement(stmtString, &incrementalVacuumStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
CachedStatement beginImmediateStmt;
|
|
rv = GetCachedStatement(NS_LITERAL_CSTRING("BEGIN IMMEDIATE;"),
|
|
&beginImmediateStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
CachedStatement commitStmt;
|
|
rv = GetCachedStatement(NS_LITERAL_CSTRING("COMMIT;"), &commitStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (aNeedsCheckpoint) {
|
|
// Freeing pages is a journaled operation, so it will require additional WAL
|
|
// space. However, we're idle and are about to checkpoint anyway, so doing a
|
|
// RESTART checkpoint here should allow us to reuse any existing space.
|
|
rv = CheckpointInternal(CheckpointMode::Restart);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Start the write transaction.
|
|
rv = beginImmediateStmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mInWriteTransaction = true;
|
|
|
|
bool freedSomePages = false;
|
|
|
|
while (aFreelistCount) {
|
|
if (NS_HasPendingEvents(currentThread)) {
|
|
// Something else wants to use the thread so roll back this transaction.
|
|
// It's ok if we never make progress here because the idle service should
|
|
// eventually reclaim this space.
|
|
rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
break;
|
|
}
|
|
|
|
rv = incrementalVacuumStmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
break;
|
|
}
|
|
|
|
freedSomePages = true;
|
|
|
|
rv = GetFreelistCount(aFreelistStatement, &aFreelistCount);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv) && freedSomePages) {
|
|
// Commit the write transaction.
|
|
rv = commitStmt->Execute();
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mInWriteTransaction = false;
|
|
} else {
|
|
NS_WARNING("Failed to commit!");
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
MOZ_ASSERT(mInWriteTransaction);
|
|
|
|
// Something failed, make sure we roll everything back.
|
|
Unused << aRollbackStatement->Execute();
|
|
|
|
mInWriteTransaction = false;
|
|
|
|
return rv;
|
|
}
|
|
|
|
*aFreedSomePages = freedSomePages;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::GetFreelistCount(CachedStatement& aCachedStatement,
|
|
uint32_t* aFreelistCount)
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aFreelistCount);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::GetFreelistCount",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv;
|
|
|
|
if (!aCachedStatement) {
|
|
rv = GetCachedStatement(NS_LITERAL_CSTRING("PRAGMA freelist_count;"),
|
|
&aCachedStatement);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
bool hasResult;
|
|
rv = aCachedStatement->ExecuteStep(&hasResult);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(hasResult);
|
|
|
|
// Make sure this statement is reset when leaving this function since we're
|
|
// not using the normal stack-based protection of CachedStatement.
|
|
mozStorageStatementScoper scoper(aCachedStatement);
|
|
|
|
int32_t freelistCount;
|
|
rv = aCachedStatement->GetInt32(0, &freelistCount);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(freelistCount >= 0);
|
|
|
|
*aFreelistCount = uint32_t(freelistCount);
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::Close()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mStorageConnection);
|
|
MOZ_ASSERT(!mDEBUGSavepointCount);
|
|
MOZ_ASSERT(!mInWriteTransaction);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::Close",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (mUpdateRefcountFunction) {
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
mStorageConnection->RemoveFunction(
|
|
NS_LITERAL_CSTRING("update_refcount")));
|
|
mUpdateRefcountFunction = nullptr;
|
|
}
|
|
|
|
mCachedStatements.Clear();
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(mStorageConnection->Close());
|
|
mStorageConnection = nullptr;
|
|
|
|
mFileManager = nullptr;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::DisableQuotaChecks()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mStorageConnection);
|
|
|
|
if (!mQuotaObject) {
|
|
MOZ_ASSERT(!mJournalQuotaObject);
|
|
|
|
nsresult rv = mStorageConnection->GetQuotaObjects(
|
|
getter_AddRefs(mQuotaObject),
|
|
getter_AddRefs(mJournalQuotaObject));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(mQuotaObject);
|
|
MOZ_ASSERT(mJournalQuotaObject);
|
|
}
|
|
|
|
mQuotaObject->DisableQuotaCheck();
|
|
mJournalQuotaObject->DisableQuotaCheck();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::EnableQuotaChecks()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mQuotaObject);
|
|
MOZ_ASSERT(mJournalQuotaObject);
|
|
|
|
RefPtr<QuotaObject> quotaObject;
|
|
RefPtr<QuotaObject> journalQuotaObject;
|
|
|
|
mQuotaObject.swap(quotaObject);
|
|
mJournalQuotaObject.swap(journalQuotaObject);
|
|
|
|
quotaObject->EnableQuotaCheck();
|
|
journalQuotaObject->EnableQuotaCheck();
|
|
|
|
int64_t fileSize;
|
|
nsresult rv = GetFileSize(quotaObject->Path(), &fileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
int64_t journalFileSize;
|
|
rv = GetFileSize(journalQuotaObject->Path(), &journalFileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
DebugOnly<bool> result =
|
|
journalQuotaObject->MaybeUpdateSize(journalFileSize, /* aTruncate */ true);
|
|
MOZ_ASSERT(result);
|
|
|
|
result = quotaObject->MaybeUpdateSize(fileSize, /* aTruncate */ true);
|
|
MOZ_ASSERT(result);
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::GetFileSize(const nsAString& aPath, int64_t* aResult)
|
|
{
|
|
MOZ_ASSERT(!aPath.IsEmpty());
|
|
MOZ_ASSERT(aResult);
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = file->InitWithPath(aPath);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
int64_t fileSize;
|
|
|
|
bool exists;
|
|
rv = file->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (exists) {
|
|
rv = file->GetFileSize(&fileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
fileSize = 0;
|
|
}
|
|
|
|
*aResult = fileSize;
|
|
return NS_OK;
|
|
}
|
|
|
|
DatabaseConnection::
|
|
CachedStatement::CachedStatement()
|
|
#ifdef DEBUG
|
|
: mDEBUGConnection(nullptr)
|
|
#endif
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
|
|
MOZ_COUNT_CTOR(DatabaseConnection::CachedStatement);
|
|
}
|
|
|
|
DatabaseConnection::
|
|
CachedStatement::~CachedStatement()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
|
|
MOZ_COUNT_DTOR(DatabaseConnection::CachedStatement);
|
|
}
|
|
|
|
DatabaseConnection::
|
|
CachedStatement::operator mozIStorageStatement*() const
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
|
|
return mStatement;
|
|
}
|
|
|
|
mozIStorageStatement*
|
|
DatabaseConnection::
|
|
CachedStatement::operator->() const
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mStatement);
|
|
|
|
return mStatement;
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::
|
|
CachedStatement::Reset()
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT_IF(mStatement, mScoper);
|
|
|
|
if (mStatement) {
|
|
mScoper.reset();
|
|
mScoper.emplace(mStatement);
|
|
}
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::
|
|
CachedStatement::Assign(DatabaseConnection* aConnection,
|
|
already_AddRefed<mozIStorageStatement> aStatement)
|
|
{
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT_IF(mDEBUGConnection, mDEBUGConnection == aConnection);
|
|
|
|
mDEBUGConnection = aConnection;
|
|
#endif
|
|
AssertIsOnConnectionThread();
|
|
|
|
mScoper.reset();
|
|
|
|
mStatement = aStatement;
|
|
|
|
if (mStatement) {
|
|
mScoper.emplace(mStatement);
|
|
}
|
|
}
|
|
|
|
DatabaseConnection::
|
|
AutoSavepoint::AutoSavepoint()
|
|
: mConnection(nullptr)
|
|
#ifdef DEBUG
|
|
, mDEBUGTransaction(nullptr)
|
|
#endif
|
|
{
|
|
MOZ_COUNT_CTOR(DatabaseConnection::AutoSavepoint);
|
|
}
|
|
|
|
DatabaseConnection::
|
|
AutoSavepoint::~AutoSavepoint()
|
|
{
|
|
MOZ_COUNT_DTOR(DatabaseConnection::AutoSavepoint);
|
|
|
|
if (mConnection) {
|
|
mConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mDEBUGTransaction);
|
|
MOZ_ASSERT(mDEBUGTransaction->GetMode() == IDBTransaction::READ_WRITE ||
|
|
mDEBUGTransaction->GetMode() ==
|
|
IDBTransaction::READ_WRITE_FLUSH ||
|
|
mDEBUGTransaction->GetMode() == IDBTransaction::CLEANUP ||
|
|
mDEBUGTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
|
|
|
|
if (NS_FAILED(mConnection->RollbackSavepoint())) {
|
|
NS_WARNING("Failed to rollback savepoint!");
|
|
}
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::
|
|
AutoSavepoint::Start(const TransactionBase* aTransaction)
|
|
{
|
|
MOZ_ASSERT(aTransaction);
|
|
MOZ_ASSERT(aTransaction->GetMode() == IDBTransaction::READ_WRITE ||
|
|
aTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH ||
|
|
aTransaction->GetMode() == IDBTransaction::CLEANUP ||
|
|
aTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
|
|
|
|
DatabaseConnection* connection = aTransaction->GetDatabase()->GetConnection();
|
|
MOZ_ASSERT(connection);
|
|
connection->AssertIsOnConnectionThread();
|
|
|
|
MOZ_ASSERT(!mConnection);
|
|
MOZ_ASSERT(!mDEBUGTransaction);
|
|
|
|
nsresult rv = connection->StartSavepoint();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mConnection = connection;
|
|
#ifdef DEBUG
|
|
mDEBUGTransaction = aTransaction;
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::
|
|
AutoSavepoint::Commit()
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mDEBUGTransaction);
|
|
|
|
nsresult rv = mConnection->ReleaseSavepoint();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mConnection = nullptr;
|
|
#ifdef DEBUG
|
|
mDEBUGTransaction = nullptr;
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
DatabaseConnection::
|
|
UpdateRefcountFunction::UpdateRefcountFunction(DatabaseConnection* aConnection,
|
|
FileManager* aFileManager)
|
|
: mConnection(aConnection)
|
|
, mFileManager(aFileManager)
|
|
, mInSavepoint(false)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aFileManager);
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::
|
|
UpdateRefcountFunction::WillCommit()
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::UpdateRefcountFunction::WillCommit",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
DatabaseUpdateFunction function(this);
|
|
for (auto iter = mFileInfoEntries.ConstIter(); !iter.Done(); iter.Next()) {
|
|
auto key = iter.Key();
|
|
FileInfoEntry* value = iter.Data();
|
|
MOZ_ASSERT(value);
|
|
|
|
if (value->mDelta && !function.Update(key, value->mDelta)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
nsresult rv = function.ErrorCode();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = CreateJournals();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::
|
|
UpdateRefcountFunction::DidCommit()
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::UpdateRefcountFunction::DidCommit",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
for (auto iter = mFileInfoEntries.ConstIter(); !iter.Done(); iter.Next()) {
|
|
FileInfoEntry* value = iter.Data();
|
|
|
|
MOZ_ASSERT(value);
|
|
|
|
if (value->mDelta) {
|
|
value->mFileInfo->UpdateDBRefs(value->mDelta);
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(RemoveJournals(mJournalsToRemoveAfterCommit))) {
|
|
NS_WARNING("RemoveJournals failed!");
|
|
}
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::
|
|
UpdateRefcountFunction::DidAbort()
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::UpdateRefcountFunction::DidAbort",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (NS_FAILED(RemoveJournals(mJournalsToRemoveAfterAbort))) {
|
|
NS_WARNING("RemoveJournals failed!");
|
|
}
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::
|
|
UpdateRefcountFunction::StartSavepoint()
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(!mInSavepoint);
|
|
MOZ_ASSERT(!mSavepointEntriesIndex.Count());
|
|
|
|
mInSavepoint = true;
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::
|
|
UpdateRefcountFunction::ReleaseSavepoint()
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mInSavepoint);
|
|
|
|
mSavepointEntriesIndex.Clear();
|
|
mInSavepoint = false;
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::
|
|
UpdateRefcountFunction::RollbackSavepoint()
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(mInSavepoint);
|
|
|
|
for (auto iter = mSavepointEntriesIndex.ConstIter();
|
|
!iter.Done(); iter.Next()) {
|
|
auto value = iter.Data();
|
|
value->mDelta -= value->mSavepointDelta;
|
|
}
|
|
|
|
mInSavepoint = false;
|
|
mSavepointEntriesIndex.Clear();
|
|
}
|
|
|
|
void
|
|
DatabaseConnection::
|
|
UpdateRefcountFunction::Reset()
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(!mSavepointEntriesIndex.Count());
|
|
MOZ_ASSERT(!mInSavepoint);
|
|
|
|
class MOZ_STACK_CLASS CustomCleanupCallback final
|
|
: public FileInfo::CustomCleanupCallback
|
|
{
|
|
nsCOMPtr<nsIFile> mDirectory;
|
|
nsCOMPtr<nsIFile> mJournalDirectory;
|
|
|
|
public:
|
|
nsresult
|
|
Cleanup(FileManager* aFileManager, int64_t aId) override
|
|
{
|
|
if (!mDirectory) {
|
|
MOZ_ASSERT(!mJournalDirectory);
|
|
|
|
mDirectory = aFileManager->GetDirectory();
|
|
if (NS_WARN_IF(!mDirectory)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mJournalDirectory = aFileManager->GetJournalDirectory();
|
|
if (NS_WARN_IF(!mJournalDirectory)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> file = aFileManager->GetFileForId(mDirectory, aId);
|
|
if (NS_WARN_IF(!file)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult rv;
|
|
int64_t fileSize;
|
|
|
|
if (aFileManager->EnforcingQuota()) {
|
|
rv = file->GetFileSize(&fileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = file->Remove(false);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (aFileManager->EnforcingQuota()) {
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
quotaManager->DecreaseUsageForOrigin(aFileManager->Type(),
|
|
aFileManager->Group(),
|
|
aFileManager->Origin(),
|
|
fileSize);
|
|
}
|
|
|
|
file = aFileManager->GetFileForId(mJournalDirectory, aId);
|
|
if (NS_WARN_IF(!file)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
rv = file->Remove(false);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
mJournalsToCreateBeforeCommit.Clear();
|
|
mJournalsToRemoveAfterCommit.Clear();
|
|
mJournalsToRemoveAfterAbort.Clear();
|
|
|
|
// FileInfo implementation automatically removes unreferenced files, but it's
|
|
// done asynchronously and with a delay. We want to remove them (and decrease
|
|
// quota usage) before we fire the commit event.
|
|
for (auto iter = mFileInfoEntries.ConstIter(); !iter.Done(); iter.Next()) {
|
|
FileInfoEntry* value = iter.Data();
|
|
|
|
MOZ_ASSERT(value);
|
|
|
|
FileInfo* fileInfo = value->mFileInfo.forget().take();
|
|
|
|
MOZ_ASSERT(fileInfo);
|
|
|
|
CustomCleanupCallback customCleanupCallback;
|
|
fileInfo->Release(&customCleanupCallback);
|
|
}
|
|
|
|
mFileInfoEntries.Clear();
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::
|
|
UpdateRefcountFunction::ProcessValue(mozIStorageValueArray* aValues,
|
|
int32_t aIndex,
|
|
UpdateType aUpdateType)
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aValues);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::UpdateRefcountFunction::ProcessValue",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
int32_t type;
|
|
nsresult rv = aValues->GetTypeOfIndex(aIndex, &type);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (type == mozIStorageValueArray::VALUE_TYPE_NULL) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsString ids;
|
|
rv = aValues->GetString(aIndex, ids);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsTArray<StructuredCloneFile> files;
|
|
rv = DeserializeStructuredCloneFiles(mFileManager, ids, files, nullptr);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < files.Length(); i++) {
|
|
const StructuredCloneFile& file = files[i];
|
|
|
|
const int64_t id = file.mFileInfo->Id();
|
|
MOZ_ASSERT(id > 0);
|
|
|
|
FileInfoEntry* entry;
|
|
if (!mFileInfoEntries.Get(id, &entry)) {
|
|
entry = new FileInfoEntry(file.mFileInfo);
|
|
mFileInfoEntries.Put(id, entry);
|
|
}
|
|
|
|
if (mInSavepoint) {
|
|
mSavepointEntriesIndex.Put(id, entry);
|
|
}
|
|
|
|
switch (aUpdateType) {
|
|
case UpdateType::Increment:
|
|
entry->mDelta++;
|
|
if (mInSavepoint) {
|
|
entry->mSavepointDelta++;
|
|
}
|
|
break;
|
|
case UpdateType::Decrement:
|
|
entry->mDelta--;
|
|
if (mInSavepoint) {
|
|
entry->mSavepointDelta--;
|
|
}
|
|
break;
|
|
default:
|
|
MOZ_CRASH("Unknown update type!");
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::
|
|
UpdateRefcountFunction::CreateJournals()
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::UpdateRefcountFunction::CreateJournals",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsCOMPtr<nsIFile> journalDirectory = mFileManager->GetJournalDirectory();
|
|
if (NS_WARN_IF(!journalDirectory)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < mJournalsToCreateBeforeCommit.Length(); i++) {
|
|
int64_t id = mJournalsToCreateBeforeCommit[i];
|
|
|
|
nsCOMPtr<nsIFile> file =
|
|
mFileManager->GetFileForId(journalDirectory, id);
|
|
if (NS_WARN_IF(!file)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mJournalsToRemoveAfterAbort.AppendElement(id);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::
|
|
UpdateRefcountFunction::RemoveJournals(const nsTArray<int64_t>& aJournals)
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::UpdateRefcountFunction::RemoveJournals",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsCOMPtr<nsIFile> journalDirectory = mFileManager->GetJournalDirectory();
|
|
if (NS_WARN_IF(!journalDirectory)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
for (uint32_t index = 0; index < aJournals.Length(); index++) {
|
|
nsCOMPtr<nsIFile> file =
|
|
mFileManager->GetFileForId(journalDirectory, aJournals[index]);
|
|
if (NS_WARN_IF(!file)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (NS_FAILED(file->Remove(false))) {
|
|
NS_WARNING("Failed to removed journal!");
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(DatabaseConnection::UpdateRefcountFunction,
|
|
mozIStorageFunction)
|
|
|
|
NS_IMETHODIMP
|
|
DatabaseConnection::
|
|
UpdateRefcountFunction::OnFunctionCall(mozIStorageValueArray* aValues,
|
|
nsIVariant** _retval)
|
|
{
|
|
MOZ_ASSERT(aValues);
|
|
MOZ_ASSERT(_retval);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::UpdateRefcountFunction::OnFunctionCall",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
uint32_t numEntries;
|
|
nsresult rv = aValues->GetNumEntries(&numEntries);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(numEntries == 2);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
int32_t type1 = mozIStorageValueArray::VALUE_TYPE_NULL;
|
|
MOZ_ASSERT(NS_SUCCEEDED(aValues->GetTypeOfIndex(0, &type1)));
|
|
|
|
int32_t type2 = mozIStorageValueArray::VALUE_TYPE_NULL;
|
|
MOZ_ASSERT(NS_SUCCEEDED(aValues->GetTypeOfIndex(1, &type2)));
|
|
|
|
MOZ_ASSERT(!(type1 == mozIStorageValueArray::VALUE_TYPE_NULL &&
|
|
type2 == mozIStorageValueArray::VALUE_TYPE_NULL));
|
|
}
|
|
#endif
|
|
|
|
rv = ProcessValue(aValues, 0, UpdateType::Decrement);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = ProcessValue(aValues, 1, UpdateType::Increment);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
DatabaseConnection::UpdateRefcountFunction::
|
|
DatabaseUpdateFunction::Update(int64_t aId,
|
|
int32_t aDelta)
|
|
{
|
|
nsresult rv = UpdateInternal(aId, aDelta);
|
|
if (NS_FAILED(rv)) {
|
|
mErrorCode = rv;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseConnection::UpdateRefcountFunction::
|
|
DatabaseUpdateFunction::UpdateInternal(int64_t aId,
|
|
int32_t aDelta)
|
|
{
|
|
MOZ_ASSERT(mFunction);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseConnection::UpdateRefcountFunction::"
|
|
"DatabaseUpdateFunction::UpdateInternal",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
DatabaseConnection* connection = mFunction->mConnection;
|
|
MOZ_ASSERT(connection);
|
|
connection->AssertIsOnConnectionThread();
|
|
|
|
MOZ_ASSERT(connection->GetStorageConnection());
|
|
|
|
nsresult rv;
|
|
if (!mUpdateStatement) {
|
|
rv = connection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"UPDATE file "
|
|
"SET refcount = refcount + :delta "
|
|
"WHERE id = :id"),
|
|
&mUpdateStatement);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
mozStorageStatementScoper updateScoper(mUpdateStatement);
|
|
|
|
rv = mUpdateStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = mUpdateStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = mUpdateStatement->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
int32_t rows;
|
|
rv = connection->GetStorageConnection()->GetAffectedRows(&rows);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (rows > 0) {
|
|
if (!mSelectStatement) {
|
|
rv = connection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"SELECT id "
|
|
"FROM file "
|
|
"WHERE id = :id"),
|
|
&mSelectStatement);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
mozStorageStatementScoper selectScoper(mSelectStatement);
|
|
|
|
rv = mSelectStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool hasResult;
|
|
rv = mSelectStatement->ExecuteStep(&hasResult);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!hasResult) {
|
|
// Don't have to create the journal here, we can create all at once,
|
|
// just before commit
|
|
mFunction->mJournalsToCreateBeforeCommit.AppendElement(aId);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!mInsertStatement) {
|
|
rv = connection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"INSERT INTO file (id, refcount) "
|
|
"VALUES(:id, :delta)"),
|
|
&mInsertStatement);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
mozStorageStatementScoper insertScoper(mInsertStatement);
|
|
|
|
rv = mInsertStatement->BindInt64ByName(NS_LITERAL_CSTRING("id"), aId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = mInsertStatement->BindInt32ByName(NS_LITERAL_CSTRING("delta"), aDelta);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = mInsertStatement->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mFunction->mJournalsToRemoveAfterCommit.AppendElement(aId);
|
|
return NS_OK;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* ConnectionPool implementation
|
|
******************************************************************************/
|
|
|
|
ConnectionPool::ConnectionPool()
|
|
: mDatabasesMutex("ConnectionPool::mDatabasesMutex")
|
|
, mIdleTimer(do_CreateInstance(NS_TIMER_CONTRACTID))
|
|
, mNextTransactionId(0)
|
|
, mTotalThreadCount(0)
|
|
, mShutdownRequested(false)
|
|
, mShutdownComplete(false)
|
|
#ifdef DEBUG
|
|
, mDEBUGOwningThread(PR_GetCurrentThread())
|
|
#endif
|
|
{
|
|
AssertIsOnOwningThread();
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mIdleTimer);
|
|
}
|
|
|
|
ConnectionPool::~ConnectionPool()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mIdleThreads.IsEmpty());
|
|
MOZ_ASSERT(mIdleDatabases.IsEmpty());
|
|
MOZ_ASSERT(!mIdleTimer);
|
|
MOZ_ASSERT(mTargetIdleTime.IsNull());
|
|
MOZ_ASSERT(!mDatabases.Count());
|
|
MOZ_ASSERT(!mTransactions.Count());
|
|
MOZ_ASSERT(mQueuedTransactions.IsEmpty());
|
|
MOZ_ASSERT(mCompleteCallbacks.IsEmpty());
|
|
MOZ_ASSERT(!mTotalThreadCount);
|
|
MOZ_ASSERT(mShutdownRequested);
|
|
MOZ_ASSERT(mShutdownComplete);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
void
|
|
ConnectionPool::AssertIsOnOwningThread() const
|
|
{
|
|
MOZ_ASSERT(mDEBUGOwningThread);
|
|
MOZ_ASSERT(PR_GetCurrentThread() == mDEBUGOwningThread);
|
|
}
|
|
|
|
#endif // DEBUG
|
|
|
|
// static
|
|
void
|
|
ConnectionPool::IdleTimerCallback(nsITimer* aTimer, void* aClosure)
|
|
{
|
|
MOZ_ASSERT(aTimer);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::IdleTimerCallback",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
auto* self = static_cast<ConnectionPool*>(aClosure);
|
|
MOZ_ASSERT(self);
|
|
MOZ_ASSERT(self->mIdleTimer);
|
|
MOZ_ASSERT(SameCOMIdentity(self->mIdleTimer, aTimer));
|
|
MOZ_ASSERT(!self->mTargetIdleTime.IsNull());
|
|
MOZ_ASSERT_IF(self->mIdleDatabases.IsEmpty(), !self->mIdleThreads.IsEmpty());
|
|
MOZ_ASSERT_IF(self->mIdleThreads.IsEmpty(), !self->mIdleDatabases.IsEmpty());
|
|
|
|
self->mTargetIdleTime = TimeStamp();
|
|
|
|
// Cheat a little.
|
|
TimeStamp now = TimeStamp::NowLoRes() + TimeDuration::FromMilliseconds(500);
|
|
|
|
uint32_t index = 0;
|
|
|
|
for (uint32_t count = self->mIdleDatabases.Length(); index < count; index++) {
|
|
IdleDatabaseInfo& info = self->mIdleDatabases[index];
|
|
|
|
if (now >= info.mIdleTime) {
|
|
if (info.mDatabaseInfo->mIdle) {
|
|
self->PerformIdleDatabaseMaintenance(info.mDatabaseInfo);
|
|
} else {
|
|
self->CloseDatabase(info.mDatabaseInfo);
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index) {
|
|
self->mIdleDatabases.RemoveElementsAt(0, index);
|
|
|
|
index = 0;
|
|
}
|
|
|
|
for (uint32_t count = self->mIdleThreads.Length(); index < count; index++) {
|
|
IdleThreadInfo& info = self->mIdleThreads[index];
|
|
MOZ_ASSERT(info.mThreadInfo.mThread);
|
|
MOZ_ASSERT(info.mThreadInfo.mRunnable);
|
|
|
|
if (now >= info.mIdleTime) {
|
|
self->ShutdownThread(info.mThreadInfo);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index) {
|
|
self->mIdleThreads.RemoveElementsAt(0, index);
|
|
}
|
|
|
|
self->AdjustIdleTimer();
|
|
}
|
|
|
|
nsresult
|
|
ConnectionPool::GetOrCreateConnection(const Database* aDatabase,
|
|
DatabaseConnection** aConnection)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aDatabase);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::GetOrCreateConnection",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
DatabaseInfo* dbInfo;
|
|
{
|
|
MutexAutoLock lock(mDatabasesMutex);
|
|
|
|
dbInfo = mDatabases.Get(aDatabase->Id());
|
|
}
|
|
|
|
MOZ_ASSERT(dbInfo);
|
|
|
|
RefPtr<DatabaseConnection> connection = dbInfo->mConnection;
|
|
if (!connection) {
|
|
MOZ_ASSERT(!dbInfo->mDEBUGConnectionThread);
|
|
|
|
nsCOMPtr<mozIStorageConnection> storageConnection;
|
|
nsresult rv =
|
|
GetStorageConnection(aDatabase->FilePath(),
|
|
aDatabase->Type(),
|
|
aDatabase->Group(),
|
|
aDatabase->Origin(),
|
|
aDatabase->TelemetryId(),
|
|
getter_AddRefs(storageConnection));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
connection =
|
|
new DatabaseConnection(storageConnection, aDatabase->GetFileManager());
|
|
|
|
rv = connection->Init();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
dbInfo->mConnection = connection;
|
|
|
|
IDB_DEBUG_LOG(("ConnectionPool created connection 0x%p for '%s'",
|
|
dbInfo->mConnection.get(),
|
|
NS_ConvertUTF16toUTF8(aDatabase->FilePath()).get()));
|
|
|
|
#ifdef DEBUG
|
|
dbInfo->mDEBUGConnectionThread = PR_GetCurrentThread();
|
|
#endif
|
|
}
|
|
|
|
dbInfo->AssertIsOnConnectionThread();
|
|
|
|
connection.forget(aConnection);
|
|
return NS_OK;
|
|
}
|
|
|
|
uint64_t
|
|
ConnectionPool::Start(const nsID& aBackgroundChildLoggingId,
|
|
const nsACString& aDatabaseId,
|
|
int64_t aLoggingSerialNumber,
|
|
const nsTArray<nsString>& aObjectStoreNames,
|
|
bool aIsWriteTransaction,
|
|
TransactionDatabaseOperationBase* aTransactionOp)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(!aDatabaseId.IsEmpty());
|
|
MOZ_ASSERT(mNextTransactionId < UINT64_MAX);
|
|
MOZ_ASSERT(!mShutdownRequested);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::Start",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const uint64_t transactionId = ++mNextTransactionId;
|
|
|
|
DatabaseInfo* dbInfo = mDatabases.Get(aDatabaseId);
|
|
|
|
const bool databaseInfoIsNew = !dbInfo;
|
|
|
|
if (databaseInfoIsNew) {
|
|
dbInfo = new DatabaseInfo(this, aDatabaseId);
|
|
|
|
MutexAutoLock lock(mDatabasesMutex);
|
|
|
|
mDatabases.Put(aDatabaseId, dbInfo);
|
|
}
|
|
|
|
auto* transactionInfo =
|
|
new TransactionInfo(dbInfo,
|
|
aBackgroundChildLoggingId,
|
|
aDatabaseId,
|
|
transactionId,
|
|
aLoggingSerialNumber,
|
|
aObjectStoreNames,
|
|
aIsWriteTransaction,
|
|
aTransactionOp);
|
|
|
|
MOZ_ASSERT(!mTransactions.Get(transactionId));
|
|
mTransactions.Put(transactionId, transactionInfo);
|
|
|
|
if (aIsWriteTransaction) {
|
|
MOZ_ASSERT(dbInfo->mWriteTransactionCount < UINT32_MAX);
|
|
dbInfo->mWriteTransactionCount++;
|
|
} else {
|
|
MOZ_ASSERT(dbInfo->mReadTransactionCount < UINT32_MAX);
|
|
dbInfo->mReadTransactionCount++;
|
|
}
|
|
|
|
auto& blockingTransactions = dbInfo->mBlockingTransactions;
|
|
|
|
for (uint32_t nameIndex = 0, nameCount = aObjectStoreNames.Length();
|
|
nameIndex < nameCount;
|
|
nameIndex++) {
|
|
const nsString& objectStoreName = aObjectStoreNames[nameIndex];
|
|
|
|
TransactionInfoPair* blockInfo = blockingTransactions.Get(objectStoreName);
|
|
if (!blockInfo) {
|
|
blockInfo = new TransactionInfoPair();
|
|
blockingTransactions.Put(objectStoreName, blockInfo);
|
|
}
|
|
|
|
// Mark what we are blocking on.
|
|
if (TransactionInfo* blockingRead = blockInfo->mLastBlockingReads) {
|
|
transactionInfo->mBlockedOn.PutEntry(blockingRead);
|
|
blockingRead->AddBlockingTransaction(transactionInfo);
|
|
}
|
|
|
|
if (aIsWriteTransaction) {
|
|
if (const uint32_t writeCount = blockInfo->mLastBlockingWrites.Length()) {
|
|
for (uint32_t writeIndex = 0; writeIndex < writeCount; writeIndex++) {
|
|
TransactionInfo* blockingWrite =
|
|
blockInfo->mLastBlockingWrites[writeIndex];
|
|
MOZ_ASSERT(blockingWrite);
|
|
|
|
transactionInfo->mBlockedOn.PutEntry(blockingWrite);
|
|
blockingWrite->AddBlockingTransaction(transactionInfo);
|
|
}
|
|
}
|
|
|
|
blockInfo->mLastBlockingReads = transactionInfo;
|
|
blockInfo->mLastBlockingWrites.Clear();
|
|
} else {
|
|
blockInfo->mLastBlockingWrites.AppendElement(transactionInfo);
|
|
}
|
|
}
|
|
|
|
if (!transactionInfo->mBlockedOn.Count()) {
|
|
Unused << ScheduleTransaction(transactionInfo,
|
|
/* aFromQueuedTransactions */ false);
|
|
}
|
|
|
|
if (!databaseInfoIsNew &&
|
|
(mIdleDatabases.RemoveElement(dbInfo) ||
|
|
mDatabasesPerformingIdleMaintenance.RemoveElement(dbInfo))) {
|
|
AdjustIdleTimer();
|
|
}
|
|
|
|
return transactionId;
|
|
}
|
|
|
|
void
|
|
ConnectionPool::Dispatch(uint64_t aTransactionId, nsIRunnable* aRunnable)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aRunnable);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::Dispatch",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
TransactionInfo* transactionInfo = mTransactions.Get(aTransactionId);
|
|
MOZ_ASSERT(transactionInfo);
|
|
MOZ_ASSERT(!transactionInfo->mFinished);
|
|
|
|
if (transactionInfo->mRunning) {
|
|
DatabaseInfo* dbInfo = transactionInfo->mDatabaseInfo;
|
|
MOZ_ASSERT(dbInfo);
|
|
MOZ_ASSERT(dbInfo->mThreadInfo.mThread);
|
|
MOZ_ASSERT(dbInfo->mThreadInfo.mRunnable);
|
|
MOZ_ASSERT(!dbInfo->mClosing);
|
|
MOZ_ASSERT_IF(transactionInfo->mIsWriteTransaction,
|
|
dbInfo->mRunningWriteTransaction == transactionInfo);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
dbInfo->mThreadInfo.mThread->Dispatch(aRunnable, NS_DISPATCH_NORMAL));
|
|
} else {
|
|
transactionInfo->mQueuedRunnables.AppendElement(aRunnable);
|
|
}
|
|
}
|
|
|
|
void
|
|
ConnectionPool::Finish(uint64_t aTransactionId, FinishCallback* aCallback)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
#ifdef DEBUG
|
|
TransactionInfo* transactionInfo = mTransactions.Get(aTransactionId);
|
|
MOZ_ASSERT(transactionInfo);
|
|
MOZ_ASSERT(!transactionInfo->mFinished);
|
|
#endif
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::Finish",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
RefPtr<FinishCallbackWrapper> wrapper =
|
|
new FinishCallbackWrapper(this, aTransactionId, aCallback);
|
|
|
|
Dispatch(aTransactionId, wrapper);
|
|
|
|
#ifdef DEBUG
|
|
MOZ_ASSERT(!transactionInfo->mFinished);
|
|
transactionInfo->mFinished = true;
|
|
#endif
|
|
}
|
|
|
|
void
|
|
ConnectionPool::WaitForDatabasesToComplete(nsTArray<nsCString>&& aDatabaseIds,
|
|
nsIRunnable* aCallback)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(!aDatabaseIds.IsEmpty());
|
|
MOZ_ASSERT(aCallback);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::WaitForDatabasesToComplete",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
bool mayRunCallbackImmediately = true;
|
|
|
|
for (uint32_t index = 0, count = aDatabaseIds.Length();
|
|
index < count;
|
|
index++) {
|
|
const nsCString& databaseId = aDatabaseIds[index];
|
|
MOZ_ASSERT(!databaseId.IsEmpty());
|
|
|
|
if (CloseDatabaseWhenIdleInternal(databaseId)) {
|
|
mayRunCallbackImmediately = false;
|
|
}
|
|
}
|
|
|
|
if (mayRunCallbackImmediately) {
|
|
Unused << aCallback->Run();
|
|
return;
|
|
}
|
|
|
|
nsAutoPtr<DatabasesCompleteCallback> callback(
|
|
new DatabasesCompleteCallback(Move(aDatabaseIds), aCallback));
|
|
mCompleteCallbacks.AppendElement(callback.forget());
|
|
}
|
|
|
|
void
|
|
ConnectionPool::Shutdown()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(!mShutdownRequested);
|
|
MOZ_ASSERT(!mShutdownComplete);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::Shutdown",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
mShutdownRequested = true;
|
|
|
|
CancelIdleTimer();
|
|
MOZ_ASSERT(mTargetIdleTime.IsNull());
|
|
|
|
mIdleTimer = nullptr;
|
|
|
|
CloseIdleDatabases();
|
|
|
|
ShutdownIdleThreads();
|
|
|
|
if (!mDatabases.Count()) {
|
|
MOZ_ASSERT(!mTransactions.Count());
|
|
|
|
Cleanup();
|
|
|
|
MOZ_ASSERT(mShutdownComplete);
|
|
return;
|
|
}
|
|
|
|
nsIThread* currentThread = NS_GetCurrentThread();
|
|
MOZ_ASSERT(currentThread);
|
|
|
|
while (!mShutdownComplete) {
|
|
MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
|
|
}
|
|
}
|
|
|
|
void
|
|
ConnectionPool::Cleanup()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mShutdownRequested);
|
|
MOZ_ASSERT(!mShutdownComplete);
|
|
MOZ_ASSERT(!mDatabases.Count());
|
|
MOZ_ASSERT(!mTransactions.Count());
|
|
MOZ_ASSERT(mIdleThreads.IsEmpty());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::Cleanup",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (!mCompleteCallbacks.IsEmpty()) {
|
|
// Run all callbacks manually now.
|
|
for (uint32_t count = mCompleteCallbacks.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
nsAutoPtr<DatabasesCompleteCallback> completeCallback(
|
|
mCompleteCallbacks[index].forget());
|
|
MOZ_ASSERT(completeCallback);
|
|
MOZ_ASSERT(completeCallback->mCallback);
|
|
|
|
Unused << completeCallback->mCallback->Run();
|
|
}
|
|
|
|
mCompleteCallbacks.Clear();
|
|
|
|
// And make sure they get processed.
|
|
nsIThread* currentThread = NS_GetCurrentThread();
|
|
MOZ_ASSERT(currentThread);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(NS_ProcessPendingEvents(currentThread));
|
|
}
|
|
|
|
mShutdownComplete = true;
|
|
}
|
|
|
|
void
|
|
ConnectionPool::AdjustIdleTimer()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mIdleTimer);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::AdjustIdleTimer",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// Figure out the next time at which we should release idle resources. This
|
|
// includes both databases and threads.
|
|
TimeStamp newTargetIdleTime;
|
|
MOZ_ASSERT(newTargetIdleTime.IsNull());
|
|
|
|
if (!mIdleDatabases.IsEmpty()) {
|
|
newTargetIdleTime = mIdleDatabases[0].mIdleTime;
|
|
}
|
|
|
|
if (!mIdleThreads.IsEmpty()) {
|
|
const TimeStamp& idleTime = mIdleThreads[0].mIdleTime;
|
|
|
|
if (newTargetIdleTime.IsNull() || idleTime < newTargetIdleTime) {
|
|
newTargetIdleTime = idleTime;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT_IF(newTargetIdleTime.IsNull(), mIdleDatabases.IsEmpty());
|
|
MOZ_ASSERT_IF(newTargetIdleTime.IsNull(), mIdleThreads.IsEmpty());
|
|
|
|
// Cancel the timer if it was running and the new target time is different.
|
|
if (!mTargetIdleTime.IsNull() &&
|
|
(newTargetIdleTime.IsNull() || mTargetIdleTime != newTargetIdleTime)) {
|
|
CancelIdleTimer();
|
|
|
|
MOZ_ASSERT(mTargetIdleTime.IsNull());
|
|
}
|
|
|
|
// Schedule the timer if we have a target time different than before.
|
|
if (!newTargetIdleTime.IsNull() &&
|
|
(mTargetIdleTime.IsNull() || mTargetIdleTime != newTargetIdleTime)) {
|
|
double delta = (newTargetIdleTime - TimeStamp::NowLoRes()).ToMilliseconds();
|
|
|
|
uint32_t delay;
|
|
if (delta > 0) {
|
|
delay = uint32_t(std::min(delta, double(UINT32_MAX)));
|
|
} else {
|
|
delay = 0;
|
|
}
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
mIdleTimer->InitWithFuncCallback(IdleTimerCallback,
|
|
this,
|
|
delay,
|
|
nsITimer::TYPE_ONE_SHOT));
|
|
|
|
mTargetIdleTime = newTargetIdleTime;
|
|
}
|
|
}
|
|
|
|
void
|
|
ConnectionPool::CancelIdleTimer()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mIdleTimer);
|
|
|
|
if (!mTargetIdleTime.IsNull()) {
|
|
MOZ_ALWAYS_SUCCEEDS(mIdleTimer->Cancel());
|
|
|
|
mTargetIdleTime = TimeStamp();
|
|
MOZ_ASSERT(mTargetIdleTime.IsNull());
|
|
}
|
|
}
|
|
|
|
void
|
|
ConnectionPool::ShutdownThread(ThreadInfo& aThreadInfo)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aThreadInfo.mThread);
|
|
MOZ_ASSERT(aThreadInfo.mRunnable);
|
|
MOZ_ASSERT(mTotalThreadCount);
|
|
|
|
RefPtr<ThreadRunnable> runnable;
|
|
aThreadInfo.mRunnable.swap(runnable);
|
|
|
|
nsCOMPtr<nsIThread> thread;
|
|
aThreadInfo.mThread.swap(thread);
|
|
|
|
IDB_DEBUG_LOG(("ConnectionPool shutting down thread %lu",
|
|
runnable->SerialNumber()));
|
|
|
|
// This should clean up the thread with the profiler.
|
|
MOZ_ALWAYS_SUCCEEDS(thread->Dispatch(runnable.forget(),
|
|
NS_DISPATCH_NORMAL));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(
|
|
NewRunnableMethod(thread, &nsIThread::Shutdown)));
|
|
|
|
mTotalThreadCount--;
|
|
}
|
|
|
|
void
|
|
ConnectionPool::CloseIdleDatabases()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mShutdownRequested);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::CloseIdleDatabases",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (!mIdleDatabases.IsEmpty()) {
|
|
for (IdleDatabaseInfo& idleInfo : mIdleDatabases) {
|
|
CloseDatabase(idleInfo.mDatabaseInfo);
|
|
}
|
|
mIdleDatabases.Clear();
|
|
}
|
|
|
|
if (!mDatabasesPerformingIdleMaintenance.IsEmpty()) {
|
|
for (DatabaseInfo* dbInfo : mDatabasesPerformingIdleMaintenance) {
|
|
MOZ_ASSERT(dbInfo);
|
|
CloseDatabase(dbInfo);
|
|
}
|
|
mDatabasesPerformingIdleMaintenance.Clear();
|
|
}
|
|
}
|
|
|
|
void
|
|
ConnectionPool::ShutdownIdleThreads()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mShutdownRequested);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::ShutdownIdleThreads",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (!mIdleThreads.IsEmpty()) {
|
|
for (uint32_t threadCount = mIdleThreads.Length(), threadIndex = 0;
|
|
threadIndex < threadCount;
|
|
threadIndex++) {
|
|
ShutdownThread(mIdleThreads[threadIndex].mThreadInfo);
|
|
}
|
|
mIdleThreads.Clear();
|
|
}
|
|
}
|
|
|
|
bool
|
|
ConnectionPool::ScheduleTransaction(TransactionInfo* aTransactionInfo,
|
|
bool aFromQueuedTransactions)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aTransactionInfo);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::ScheduleTransaction",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
DatabaseInfo* dbInfo = aTransactionInfo->mDatabaseInfo;
|
|
MOZ_ASSERT(dbInfo);
|
|
|
|
dbInfo->mIdle = false;
|
|
|
|
if (dbInfo->mClosing) {
|
|
MOZ_ASSERT(!mIdleDatabases.Contains(dbInfo));
|
|
MOZ_ASSERT(
|
|
!dbInfo->mTransactionsScheduledDuringClose.Contains(aTransactionInfo));
|
|
|
|
dbInfo->mTransactionsScheduledDuringClose.AppendElement(aTransactionInfo);
|
|
return true;
|
|
}
|
|
|
|
if (!dbInfo->mThreadInfo.mThread) {
|
|
MOZ_ASSERT(!dbInfo->mThreadInfo.mRunnable);
|
|
|
|
if (mIdleThreads.IsEmpty()) {
|
|
bool created = false;
|
|
|
|
if (mTotalThreadCount < kMaxConnectionThreadCount) {
|
|
// This will set the thread up with the profiler.
|
|
RefPtr<ThreadRunnable> runnable = new ThreadRunnable();
|
|
|
|
nsCOMPtr<nsIThread> newThread;
|
|
nsresult rv =
|
|
NS_NewNamedThread(runnable->GetThreadName(),
|
|
getter_AddRefs(newThread), runnable);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
MOZ_ASSERT(newThread);
|
|
|
|
IDB_DEBUG_LOG(("ConnectionPool created thread %lu",
|
|
runnable->SerialNumber()));
|
|
|
|
dbInfo->mThreadInfo.mThread.swap(newThread);
|
|
dbInfo->mThreadInfo.mRunnable.swap(runnable);
|
|
|
|
mTotalThreadCount++;
|
|
created = true;
|
|
} else {
|
|
NS_WARNING("Failed to make new thread!");
|
|
}
|
|
} else if (!mDatabasesPerformingIdleMaintenance.IsEmpty()) {
|
|
// We need a thread right now so force all idle processing to stop by
|
|
// posting a dummy runnable to each thread that might be doing idle
|
|
// maintenance.
|
|
nsCOMPtr<nsIRunnable> runnable = new Runnable();
|
|
|
|
for (uint32_t index = mDatabasesPerformingIdleMaintenance.Length();
|
|
index > 0;
|
|
index--) {
|
|
DatabaseInfo* dbInfo = mDatabasesPerformingIdleMaintenance[index - 1];
|
|
MOZ_ASSERT(dbInfo);
|
|
MOZ_ASSERT(dbInfo->mThreadInfo.mThread);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
dbInfo->mThreadInfo.mThread->Dispatch(runnable.forget(),
|
|
NS_DISPATCH_NORMAL));
|
|
}
|
|
}
|
|
|
|
if (!created) {
|
|
if (!aFromQueuedTransactions) {
|
|
MOZ_ASSERT(!mQueuedTransactions.Contains(aTransactionInfo));
|
|
mQueuedTransactions.AppendElement(aTransactionInfo);
|
|
}
|
|
return false;
|
|
}
|
|
} else {
|
|
const uint32_t lastIndex = mIdleThreads.Length() - 1;
|
|
|
|
ThreadInfo& threadInfo = mIdleThreads[lastIndex].mThreadInfo;
|
|
|
|
dbInfo->mThreadInfo.mRunnable.swap(threadInfo.mRunnable);
|
|
dbInfo->mThreadInfo.mThread.swap(threadInfo.mThread);
|
|
|
|
mIdleThreads.RemoveElementAt(lastIndex);
|
|
|
|
AdjustIdleTimer();
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(dbInfo->mThreadInfo.mThread);
|
|
MOZ_ASSERT(dbInfo->mThreadInfo.mRunnable);
|
|
|
|
if (aTransactionInfo->mIsWriteTransaction) {
|
|
if (dbInfo->mRunningWriteTransaction) {
|
|
// SQLite only allows one write transaction at a time so queue this
|
|
// transaction for later.
|
|
MOZ_ASSERT(
|
|
!dbInfo->mScheduledWriteTransactions.Contains(aTransactionInfo));
|
|
|
|
dbInfo->mScheduledWriteTransactions.AppendElement(aTransactionInfo);
|
|
return true;
|
|
}
|
|
|
|
dbInfo->mRunningWriteTransaction = aTransactionInfo;
|
|
dbInfo->mNeedsCheckpoint = true;
|
|
}
|
|
|
|
MOZ_ASSERT(!aTransactionInfo->mRunning);
|
|
aTransactionInfo->mRunning = true;
|
|
|
|
nsTArray<nsCOMPtr<nsIRunnable>>& queuedRunnables =
|
|
aTransactionInfo->mQueuedRunnables;
|
|
|
|
if (!queuedRunnables.IsEmpty()) {
|
|
for (uint32_t index = 0, count = queuedRunnables.Length();
|
|
index < count;
|
|
index++) {
|
|
nsCOMPtr<nsIRunnable> runnable;
|
|
queuedRunnables[index].swap(runnable);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
dbInfo->mThreadInfo.mThread->Dispatch(runnable.forget(),
|
|
NS_DISPATCH_NORMAL));
|
|
}
|
|
|
|
queuedRunnables.Clear();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ConnectionPool::NoteFinishedTransaction(uint64_t aTransactionId)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::NoteFinishedTransaction",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
TransactionInfo* transactionInfo = mTransactions.Get(aTransactionId);
|
|
MOZ_ASSERT(transactionInfo);
|
|
MOZ_ASSERT(transactionInfo->mRunning);
|
|
MOZ_ASSERT(transactionInfo->mFinished);
|
|
|
|
transactionInfo->mRunning = false;
|
|
|
|
DatabaseInfo* dbInfo = transactionInfo->mDatabaseInfo;
|
|
MOZ_ASSERT(dbInfo);
|
|
MOZ_ASSERT(mDatabases.Get(transactionInfo->mDatabaseId) == dbInfo);
|
|
MOZ_ASSERT(dbInfo->mThreadInfo.mThread);
|
|
MOZ_ASSERT(dbInfo->mThreadInfo.mRunnable);
|
|
|
|
// Schedule the next write transaction if there are any queued.
|
|
if (dbInfo->mRunningWriteTransaction == transactionInfo) {
|
|
MOZ_ASSERT(transactionInfo->mIsWriteTransaction);
|
|
MOZ_ASSERT(dbInfo->mNeedsCheckpoint);
|
|
|
|
dbInfo->mRunningWriteTransaction = nullptr;
|
|
|
|
if (!dbInfo->mScheduledWriteTransactions.IsEmpty()) {
|
|
TransactionInfo* nextWriteTransaction =
|
|
dbInfo->mScheduledWriteTransactions[0];
|
|
MOZ_ASSERT(nextWriteTransaction);
|
|
|
|
dbInfo->mScheduledWriteTransactions.RemoveElementAt(0);
|
|
|
|
MOZ_ALWAYS_TRUE(ScheduleTransaction(nextWriteTransaction,
|
|
/* aFromQueuedTransactions */ false));
|
|
}
|
|
}
|
|
|
|
const nsTArray<nsString>& objectStoreNames =
|
|
transactionInfo->mObjectStoreNames;
|
|
|
|
for (uint32_t index = 0, count = objectStoreNames.Length();
|
|
index < count;
|
|
index++) {
|
|
TransactionInfoPair* blockInfo =
|
|
dbInfo->mBlockingTransactions.Get(objectStoreNames[index]);
|
|
MOZ_ASSERT(blockInfo);
|
|
|
|
if (transactionInfo->mIsWriteTransaction &&
|
|
blockInfo->mLastBlockingReads == transactionInfo) {
|
|
blockInfo->mLastBlockingReads = nullptr;
|
|
}
|
|
|
|
blockInfo->mLastBlockingWrites.RemoveElement(transactionInfo);
|
|
}
|
|
|
|
transactionInfo->RemoveBlockingTransactions();
|
|
|
|
if (transactionInfo->mIsWriteTransaction) {
|
|
MOZ_ASSERT(dbInfo->mWriteTransactionCount);
|
|
dbInfo->mWriteTransactionCount--;
|
|
} else {
|
|
MOZ_ASSERT(dbInfo->mReadTransactionCount);
|
|
dbInfo->mReadTransactionCount--;
|
|
}
|
|
|
|
mTransactions.Remove(aTransactionId);
|
|
|
|
#ifdef DEBUG
|
|
// That just deleted |transactionInfo|.
|
|
transactionInfo = nullptr;
|
|
#endif
|
|
|
|
if (!dbInfo->TotalTransactionCount()) {
|
|
MOZ_ASSERT(!dbInfo->mIdle);
|
|
dbInfo->mIdle = true;
|
|
|
|
NoteIdleDatabase(dbInfo);
|
|
}
|
|
}
|
|
|
|
void
|
|
ConnectionPool::ScheduleQueuedTransactions(ThreadInfo& aThreadInfo)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aThreadInfo.mThread);
|
|
MOZ_ASSERT(aThreadInfo.mRunnable);
|
|
MOZ_ASSERT(!mQueuedTransactions.IsEmpty());
|
|
MOZ_ASSERT(!mIdleThreads.Contains(aThreadInfo));
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::ScheduleQueuedTransactions",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
mIdleThreads.InsertElementSorted(aThreadInfo);
|
|
|
|
aThreadInfo.mRunnable = nullptr;
|
|
aThreadInfo.mThread = nullptr;
|
|
|
|
uint32_t index = 0;
|
|
for (uint32_t count = mQueuedTransactions.Length(); index < count; index++) {
|
|
if (!ScheduleTransaction(mQueuedTransactions[index],
|
|
/* aFromQueuedTransactions */ true)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (index) {
|
|
mQueuedTransactions.RemoveElementsAt(0, index);
|
|
}
|
|
|
|
AdjustIdleTimer();
|
|
}
|
|
|
|
void
|
|
ConnectionPool::NoteIdleDatabase(DatabaseInfo* aDatabaseInfo)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aDatabaseInfo);
|
|
MOZ_ASSERT(!aDatabaseInfo->TotalTransactionCount());
|
|
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mThread);
|
|
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mRunnable);
|
|
MOZ_ASSERT(!mIdleDatabases.Contains(aDatabaseInfo));
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::NoteIdleDatabase",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const bool otherDatabasesWaiting = !mQueuedTransactions.IsEmpty();
|
|
|
|
if (mShutdownRequested ||
|
|
otherDatabasesWaiting ||
|
|
aDatabaseInfo->mCloseOnIdle) {
|
|
// Make sure we close the connection if we're shutting down or giving the
|
|
// thread to another database.
|
|
CloseDatabase(aDatabaseInfo);
|
|
|
|
if (otherDatabasesWaiting) {
|
|
// Let another database use this thread.
|
|
ScheduleQueuedTransactions(aDatabaseInfo->mThreadInfo);
|
|
} else if (mShutdownRequested) {
|
|
// If there are no other databases that need to run then we can shut this
|
|
// thread down immediately instead of going through the idle thread
|
|
// mechanism.
|
|
ShutdownThread(aDatabaseInfo->mThreadInfo);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
mIdleDatabases.InsertElementSorted(aDatabaseInfo);
|
|
|
|
AdjustIdleTimer();
|
|
}
|
|
|
|
void
|
|
ConnectionPool::NoteClosedDatabase(DatabaseInfo* aDatabaseInfo)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aDatabaseInfo);
|
|
MOZ_ASSERT(aDatabaseInfo->mClosing);
|
|
MOZ_ASSERT(!mIdleDatabases.Contains(aDatabaseInfo));
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::NoteClosedDatabase",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
aDatabaseInfo->mClosing = false;
|
|
|
|
// Figure out what to do with this database's thread. It may have already been
|
|
// given to another database, in which case there's nothing to do here.
|
|
// Otherwise we prioritize the thread as follows:
|
|
// 1. Databases that haven't had an opportunity to run at all are highest
|
|
// priority. Those live in the |mQueuedTransactions| list.
|
|
// 2. If this database has additional transactions that were started after
|
|
// we began closing the connection then the thread can be reused for
|
|
// those transactions.
|
|
// 3. If we're shutting down then we can get rid of the thread.
|
|
// 4. Finally, if nothing above took the thread then we can add it to our
|
|
// list of idle threads. It may be reused or it may time out. If we have
|
|
// too many idle threads then we will shut down the oldest.
|
|
if (aDatabaseInfo->mThreadInfo.mThread) {
|
|
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mRunnable);
|
|
|
|
if (!mQueuedTransactions.IsEmpty()) {
|
|
// Give the thread to another database.
|
|
ScheduleQueuedTransactions(aDatabaseInfo->mThreadInfo);
|
|
} else if (!aDatabaseInfo->TotalTransactionCount()) {
|
|
if (mShutdownRequested) {
|
|
ShutdownThread(aDatabaseInfo->mThreadInfo);
|
|
} else {
|
|
MOZ_ASSERT(!mIdleThreads.Contains(aDatabaseInfo->mThreadInfo));
|
|
|
|
mIdleThreads.InsertElementSorted(aDatabaseInfo->mThreadInfo);
|
|
|
|
aDatabaseInfo->mThreadInfo.mRunnable = nullptr;
|
|
aDatabaseInfo->mThreadInfo.mThread = nullptr;
|
|
|
|
if (mIdleThreads.Length() > kMaxIdleConnectionThreadCount) {
|
|
ShutdownThread(mIdleThreads[0].mThreadInfo);
|
|
mIdleThreads.RemoveElementAt(0);
|
|
}
|
|
|
|
AdjustIdleTimer();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Schedule any transactions that were started while we were closing the
|
|
// connection.
|
|
if (aDatabaseInfo->TotalTransactionCount()) {
|
|
nsTArray<TransactionInfo*>& scheduledTransactions =
|
|
aDatabaseInfo->mTransactionsScheduledDuringClose;
|
|
|
|
MOZ_ASSERT(!scheduledTransactions.IsEmpty());
|
|
|
|
for (uint32_t index = 0, count = scheduledTransactions.Length();
|
|
index < count;
|
|
index++) {
|
|
Unused << ScheduleTransaction(scheduledTransactions[index],
|
|
/* aFromQueuedTransactions */ false);
|
|
}
|
|
|
|
scheduledTransactions.Clear();
|
|
|
|
return;
|
|
}
|
|
|
|
// There are no more transactions and the connection has been closed. We're
|
|
// done with this database.
|
|
{
|
|
MutexAutoLock lock(mDatabasesMutex);
|
|
|
|
mDatabases.Remove(aDatabaseInfo->mDatabaseId);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
// That just deleted |aDatabaseInfo|.
|
|
aDatabaseInfo = nullptr;
|
|
#endif
|
|
|
|
// See if we need to fire any complete callbacks now that the database is
|
|
// finished.
|
|
for (uint32_t index = 0;
|
|
index < mCompleteCallbacks.Length();
|
|
/* conditionally incremented */) {
|
|
if (MaybeFireCallback(mCompleteCallbacks[index])) {
|
|
mCompleteCallbacks.RemoveElementAt(index);
|
|
} else {
|
|
index++;
|
|
}
|
|
}
|
|
|
|
// If that was the last database and we're supposed to be shutting down then
|
|
// we are finished.
|
|
if (mShutdownRequested && !mDatabases.Count()) {
|
|
MOZ_ASSERT(!mTransactions.Count());
|
|
Cleanup();
|
|
}
|
|
}
|
|
|
|
bool
|
|
ConnectionPool::MaybeFireCallback(DatabasesCompleteCallback* aCallback)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aCallback);
|
|
MOZ_ASSERT(!aCallback->mDatabaseIds.IsEmpty());
|
|
MOZ_ASSERT(aCallback->mCallback);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::MaybeFireCallback",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
for (uint32_t count = aCallback->mDatabaseIds.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
const nsCString& databaseId = aCallback->mDatabaseIds[index];
|
|
MOZ_ASSERT(!databaseId.IsEmpty());
|
|
|
|
if (mDatabases.Get(databaseId)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Unused << aCallback->mCallback->Run();
|
|
return true;
|
|
}
|
|
|
|
void
|
|
ConnectionPool::PerformIdleDatabaseMaintenance(DatabaseInfo* aDatabaseInfo)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aDatabaseInfo);
|
|
MOZ_ASSERT(!aDatabaseInfo->TotalTransactionCount());
|
|
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mThread);
|
|
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mRunnable);
|
|
MOZ_ASSERT(aDatabaseInfo->mIdle);
|
|
MOZ_ASSERT(!aDatabaseInfo->mCloseOnIdle);
|
|
MOZ_ASSERT(!aDatabaseInfo->mClosing);
|
|
MOZ_ASSERT(mIdleDatabases.Contains(aDatabaseInfo));
|
|
MOZ_ASSERT(!mDatabasesPerformingIdleMaintenance.Contains(aDatabaseInfo));
|
|
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
new IdleConnectionRunnable(aDatabaseInfo, aDatabaseInfo->mNeedsCheckpoint);
|
|
|
|
aDatabaseInfo->mNeedsCheckpoint = false;
|
|
aDatabaseInfo->mIdle = false;
|
|
|
|
mDatabasesPerformingIdleMaintenance.AppendElement(aDatabaseInfo);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aDatabaseInfo->mThreadInfo.mThread->Dispatch(runnable.forget(),
|
|
NS_DISPATCH_NORMAL));
|
|
}
|
|
|
|
void
|
|
ConnectionPool::CloseDatabase(DatabaseInfo* aDatabaseInfo)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aDatabaseInfo);
|
|
MOZ_ASSERT(!aDatabaseInfo->TotalTransactionCount());
|
|
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mThread);
|
|
MOZ_ASSERT(aDatabaseInfo->mThreadInfo.mRunnable);
|
|
MOZ_ASSERT(!aDatabaseInfo->mClosing);
|
|
|
|
aDatabaseInfo->mIdle = false;
|
|
aDatabaseInfo->mNeedsCheckpoint = false;
|
|
aDatabaseInfo->mClosing = true;
|
|
|
|
nsCOMPtr<nsIRunnable> runnable = new CloseConnectionRunnable(aDatabaseInfo);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aDatabaseInfo->mThreadInfo.mThread->Dispatch(runnable.forget(),
|
|
NS_DISPATCH_NORMAL));
|
|
}
|
|
|
|
bool
|
|
ConnectionPool::CloseDatabaseWhenIdleInternal(const nsACString& aDatabaseId)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(!aDatabaseId.IsEmpty());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::CloseDatabaseWhenIdleInternal",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (DatabaseInfo* dbInfo = mDatabases.Get(aDatabaseId)) {
|
|
if (mIdleDatabases.RemoveElement(dbInfo) ||
|
|
mDatabasesPerformingIdleMaintenance.RemoveElement(dbInfo)) {
|
|
CloseDatabase(dbInfo);
|
|
AdjustIdleTimer();
|
|
} else {
|
|
dbInfo->mCloseOnIdle = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
ConnectionPool::
|
|
ConnectionRunnable::ConnectionRunnable(DatabaseInfo* aDatabaseInfo)
|
|
: mDatabaseInfo(aDatabaseInfo)
|
|
, mOwningThread(do_GetCurrentThread())
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aDatabaseInfo);
|
|
MOZ_ASSERT(aDatabaseInfo->mConnectionPool);
|
|
aDatabaseInfo->mConnectionPool->AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mOwningThread);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED0(ConnectionPool::IdleConnectionRunnable,
|
|
ConnectionPool::ConnectionRunnable)
|
|
|
|
NS_IMETHODIMP
|
|
ConnectionPool::
|
|
IdleConnectionRunnable::Run()
|
|
{
|
|
MOZ_ASSERT(mDatabaseInfo);
|
|
MOZ_ASSERT(!mDatabaseInfo->mIdle);
|
|
|
|
nsCOMPtr<nsIEventTarget> owningThread;
|
|
mOwningThread.swap(owningThread);
|
|
|
|
if (owningThread) {
|
|
mDatabaseInfo->AssertIsOnConnectionThread();
|
|
|
|
// The connection could be null if EnsureConnection() didn't run or was not
|
|
// successful in TransactionDatabaseOperationBase::RunOnConnectionThread().
|
|
if (mDatabaseInfo->mConnection) {
|
|
mDatabaseInfo->mConnection->DoIdleProcessing(mNeedsCheckpoint);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
owningThread->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
RefPtr<ConnectionPool> connectionPool = mDatabaseInfo->mConnectionPool;
|
|
MOZ_ASSERT(connectionPool);
|
|
|
|
if (mDatabaseInfo->mClosing || mDatabaseInfo->TotalTransactionCount()) {
|
|
MOZ_ASSERT(!connectionPool->
|
|
mDatabasesPerformingIdleMaintenance.Contains(mDatabaseInfo));
|
|
} else {
|
|
MOZ_ALWAYS_TRUE(
|
|
connectionPool->
|
|
mDatabasesPerformingIdleMaintenance.RemoveElement(mDatabaseInfo));
|
|
|
|
connectionPool->NoteIdleDatabase(mDatabaseInfo);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED0(ConnectionPool::CloseConnectionRunnable,
|
|
ConnectionPool::ConnectionRunnable)
|
|
|
|
NS_IMETHODIMP
|
|
ConnectionPool::
|
|
CloseConnectionRunnable::Run()
|
|
{
|
|
MOZ_ASSERT(mDatabaseInfo);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::CloseConnectionRunnable::Run",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (mOwningThread) {
|
|
MOZ_ASSERT(mDatabaseInfo->mClosing);
|
|
|
|
nsCOMPtr<nsIEventTarget> owningThread;
|
|
mOwningThread.swap(owningThread);
|
|
|
|
// The connection could be null if EnsureConnection() didn't run or was not
|
|
// successful in TransactionDatabaseOperationBase::RunOnConnectionThread().
|
|
if (mDatabaseInfo->mConnection) {
|
|
mDatabaseInfo->AssertIsOnConnectionThread();
|
|
|
|
mDatabaseInfo->mConnection->Close();
|
|
|
|
IDB_DEBUG_LOG(("ConnectionPool closed connection 0x%p",
|
|
mDatabaseInfo->mConnection.get()));
|
|
|
|
mDatabaseInfo->mConnection = nullptr;
|
|
|
|
#ifdef DEBUG
|
|
mDatabaseInfo->mDEBUGConnectionThread = nullptr;
|
|
#endif
|
|
}
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
owningThread->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<ConnectionPool> connectionPool = mDatabaseInfo->mConnectionPool;
|
|
MOZ_ASSERT(connectionPool);
|
|
|
|
connectionPool->NoteClosedDatabase(mDatabaseInfo);
|
|
return NS_OK;
|
|
}
|
|
|
|
ConnectionPool::
|
|
DatabaseInfo::DatabaseInfo(ConnectionPool* aConnectionPool,
|
|
const nsACString& aDatabaseId)
|
|
: mConnectionPool(aConnectionPool)
|
|
, mDatabaseId(aDatabaseId)
|
|
, mRunningWriteTransaction(nullptr)
|
|
, mReadTransactionCount(0)
|
|
, mWriteTransactionCount(0)
|
|
, mNeedsCheckpoint(false)
|
|
, mIdle(false)
|
|
, mCloseOnIdle(false)
|
|
, mClosing(false)
|
|
#ifdef DEBUG
|
|
, mDEBUGConnectionThread(nullptr)
|
|
#endif
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aConnectionPool);
|
|
aConnectionPool->AssertIsOnOwningThread();
|
|
MOZ_ASSERT(!aDatabaseId.IsEmpty());
|
|
|
|
MOZ_COUNT_CTOR(ConnectionPool::DatabaseInfo);
|
|
}
|
|
|
|
ConnectionPool::
|
|
DatabaseInfo::~DatabaseInfo()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mConnection);
|
|
MOZ_ASSERT(mScheduledWriteTransactions.IsEmpty());
|
|
MOZ_ASSERT(!mRunningWriteTransaction);
|
|
MOZ_ASSERT(!mThreadInfo.mThread);
|
|
MOZ_ASSERT(!mThreadInfo.mRunnable);
|
|
MOZ_ASSERT(!TotalTransactionCount());
|
|
|
|
MOZ_COUNT_DTOR(ConnectionPool::DatabaseInfo);
|
|
}
|
|
|
|
ConnectionPool::
|
|
DatabasesCompleteCallback::DatabasesCompleteCallback(
|
|
nsTArray<nsCString>&& aDatabaseIds,
|
|
nsIRunnable* aCallback)
|
|
: mDatabaseIds(Move(aDatabaseIds))
|
|
, mCallback(aCallback)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mDatabaseIds.IsEmpty());
|
|
MOZ_ASSERT(aCallback);
|
|
|
|
MOZ_COUNT_CTOR(ConnectionPool::DatabasesCompleteCallback);
|
|
}
|
|
|
|
ConnectionPool::
|
|
DatabasesCompleteCallback::~DatabasesCompleteCallback()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
MOZ_COUNT_DTOR(ConnectionPool::DatabasesCompleteCallback);
|
|
}
|
|
|
|
ConnectionPool::
|
|
FinishCallbackWrapper::FinishCallbackWrapper(ConnectionPool* aConnectionPool,
|
|
uint64_t aTransactionId,
|
|
FinishCallback* aCallback)
|
|
: mConnectionPool(aConnectionPool)
|
|
, mCallback(aCallback)
|
|
, mOwningThread(do_GetCurrentThread())
|
|
, mTransactionId(aTransactionId)
|
|
, mHasRunOnce(false)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aConnectionPool);
|
|
MOZ_ASSERT(aCallback);
|
|
MOZ_ASSERT(mOwningThread);
|
|
}
|
|
|
|
ConnectionPool::
|
|
FinishCallbackWrapper::~FinishCallbackWrapper()
|
|
{
|
|
MOZ_ASSERT(!mConnectionPool);
|
|
MOZ_ASSERT(!mCallback);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED0(ConnectionPool::FinishCallbackWrapper, Runnable)
|
|
|
|
nsresult
|
|
ConnectionPool::
|
|
FinishCallbackWrapper::Run()
|
|
{
|
|
MOZ_ASSERT(mConnectionPool);
|
|
MOZ_ASSERT(mCallback);
|
|
MOZ_ASSERT(mOwningThread);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::FinishCallbackWrapper::Run",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (!mHasRunOnce) {
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
mHasRunOnce = true;
|
|
|
|
Unused << mCallback->Run();
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
mConnectionPool->AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mHasRunOnce);
|
|
|
|
RefPtr<ConnectionPool> connectionPool = Move(mConnectionPool);
|
|
RefPtr<FinishCallback> callback = Move(mCallback);
|
|
|
|
callback->TransactionFinishedBeforeUnblock();
|
|
|
|
connectionPool->NoteFinishedTransaction(mTransactionId);
|
|
|
|
callback->TransactionFinishedAfterUnblock();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
uint32_t ConnectionPool::ThreadRunnable::sNextSerialNumber = 0;
|
|
|
|
ConnectionPool::
|
|
ThreadRunnable::ThreadRunnable()
|
|
: mSerialNumber(++sNextSerialNumber)
|
|
, mFirstRun(true)
|
|
, mContinueRunning(true)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
}
|
|
|
|
ConnectionPool::
|
|
ThreadRunnable::~ThreadRunnable()
|
|
{
|
|
MOZ_ASSERT(!mFirstRun);
|
|
MOZ_ASSERT(!mContinueRunning);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED0(ConnectionPool::ThreadRunnable, Runnable)
|
|
|
|
nsresult
|
|
ConnectionPool::
|
|
ThreadRunnable::Run()
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(mContinueRunning);
|
|
|
|
if (!mFirstRun) {
|
|
mContinueRunning = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
mFirstRun = false;
|
|
|
|
{
|
|
// Scope for the profiler label.
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ConnectionPool::ThreadRunnable::Run",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsIThread* currentThread = NS_GetCurrentThread();
|
|
MOZ_ASSERT(currentThread);
|
|
|
|
#ifdef DEBUG
|
|
if (kDEBUGTransactionThreadPriority !=
|
|
nsISupportsPriority::PRIORITY_NORMAL) {
|
|
NS_WARNING("ConnectionPool thread debugging enabled, priority has been "
|
|
"modified!");
|
|
|
|
nsCOMPtr<nsISupportsPriority> thread = do_QueryInterface(currentThread);
|
|
MOZ_ASSERT(thread);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
thread->SetPriority(kDEBUGTransactionThreadPriority));
|
|
}
|
|
|
|
if (kDEBUGTransactionThreadSleepMS) {
|
|
NS_WARNING("TransactionThreadPool thread debugging enabled, sleeping "
|
|
"after every event!");
|
|
}
|
|
#endif // DEBUG
|
|
|
|
while (mContinueRunning) {
|
|
MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
|
|
|
|
#ifdef DEBUG
|
|
if (kDEBUGTransactionThreadSleepMS) {
|
|
MOZ_ALWAYS_TRUE(
|
|
PR_Sleep(PR_MillisecondsToInterval(kDEBUGTransactionThreadSleepMS)) ==
|
|
PR_SUCCESS);
|
|
}
|
|
#endif // DEBUG
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
ConnectionPool::
|
|
ThreadInfo::ThreadInfo()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
MOZ_COUNT_CTOR(ConnectionPool::ThreadInfo);
|
|
}
|
|
|
|
ConnectionPool::
|
|
ThreadInfo::ThreadInfo(const ThreadInfo& aOther)
|
|
: mThread(aOther.mThread)
|
|
, mRunnable(aOther.mRunnable)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aOther.mThread);
|
|
MOZ_ASSERT(aOther.mRunnable);
|
|
|
|
MOZ_COUNT_CTOR(ConnectionPool::ThreadInfo);
|
|
}
|
|
|
|
ConnectionPool::
|
|
ThreadInfo::~ThreadInfo()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
MOZ_COUNT_DTOR(ConnectionPool::ThreadInfo);
|
|
}
|
|
|
|
ConnectionPool::
|
|
IdleResource::IdleResource(const TimeStamp& aIdleTime)
|
|
: mIdleTime(aIdleTime)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!aIdleTime.IsNull());
|
|
|
|
MOZ_COUNT_CTOR(ConnectionPool::IdleResource);
|
|
}
|
|
|
|
ConnectionPool::
|
|
IdleResource::~IdleResource()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
MOZ_COUNT_DTOR(ConnectionPool::IdleResource);
|
|
}
|
|
|
|
ConnectionPool::
|
|
IdleDatabaseInfo::IdleDatabaseInfo(DatabaseInfo* aDatabaseInfo)
|
|
: IdleResource(TimeStamp::NowLoRes() +
|
|
(aDatabaseInfo->mIdle ?
|
|
TimeDuration::FromMilliseconds(kConnectionIdleMaintenanceMS) :
|
|
TimeDuration::FromMilliseconds(kConnectionIdleCloseMS)))
|
|
, mDatabaseInfo(aDatabaseInfo)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aDatabaseInfo);
|
|
|
|
MOZ_COUNT_CTOR(ConnectionPool::IdleDatabaseInfo);
|
|
}
|
|
|
|
ConnectionPool::
|
|
IdleDatabaseInfo::~IdleDatabaseInfo()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mDatabaseInfo);
|
|
|
|
MOZ_COUNT_DTOR(ConnectionPool::IdleDatabaseInfo);
|
|
}
|
|
|
|
ConnectionPool::
|
|
IdleThreadInfo::IdleThreadInfo(const ThreadInfo& aThreadInfo)
|
|
: IdleResource(TimeStamp::NowLoRes() +
|
|
TimeDuration::FromMilliseconds(kConnectionThreadIdleMS))
|
|
, mThreadInfo(aThreadInfo)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aThreadInfo.mRunnable);
|
|
MOZ_ASSERT(aThreadInfo.mThread);
|
|
|
|
MOZ_COUNT_CTOR(ConnectionPool::IdleThreadInfo);
|
|
}
|
|
|
|
ConnectionPool::
|
|
IdleThreadInfo::~IdleThreadInfo()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
MOZ_COUNT_DTOR(ConnectionPool::IdleThreadInfo);
|
|
}
|
|
|
|
ConnectionPool::
|
|
TransactionInfo::TransactionInfo(
|
|
DatabaseInfo* aDatabaseInfo,
|
|
const nsID& aBackgroundChildLoggingId,
|
|
const nsACString& aDatabaseId,
|
|
uint64_t aTransactionId,
|
|
int64_t aLoggingSerialNumber,
|
|
const nsTArray<nsString>& aObjectStoreNames,
|
|
bool aIsWriteTransaction,
|
|
TransactionDatabaseOperationBase* aTransactionOp)
|
|
: mDatabaseInfo(aDatabaseInfo)
|
|
, mBackgroundChildLoggingId(aBackgroundChildLoggingId)
|
|
, mDatabaseId(aDatabaseId)
|
|
, mTransactionId(aTransactionId)
|
|
, mLoggingSerialNumber(aLoggingSerialNumber)
|
|
, mObjectStoreNames(aObjectStoreNames)
|
|
, mIsWriteTransaction(aIsWriteTransaction)
|
|
, mRunning(false)
|
|
#ifdef DEBUG
|
|
, mFinished(false)
|
|
#endif
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aDatabaseInfo);
|
|
aDatabaseInfo->mConnectionPool->AssertIsOnOwningThread();
|
|
|
|
MOZ_COUNT_CTOR(ConnectionPool::TransactionInfo);
|
|
|
|
if (aTransactionOp) {
|
|
mQueuedRunnables.AppendElement(aTransactionOp);
|
|
}
|
|
}
|
|
|
|
ConnectionPool::
|
|
TransactionInfo::~TransactionInfo()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mBlockedOn.Count());
|
|
MOZ_ASSERT(mQueuedRunnables.IsEmpty());
|
|
MOZ_ASSERT(!mRunning);
|
|
MOZ_ASSERT(mFinished);
|
|
|
|
MOZ_COUNT_DTOR(ConnectionPool::TransactionInfo);
|
|
}
|
|
|
|
void
|
|
ConnectionPool::
|
|
TransactionInfo::AddBlockingTransaction(TransactionInfo* aTransactionInfo)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aTransactionInfo);
|
|
|
|
if (!mBlocking.Contains(aTransactionInfo)) {
|
|
mBlocking.PutEntry(aTransactionInfo);
|
|
mBlockingOrdered.AppendElement(aTransactionInfo);
|
|
}
|
|
}
|
|
|
|
void
|
|
ConnectionPool::
|
|
TransactionInfo::RemoveBlockingTransactions()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
for (uint32_t index = 0, count = mBlockingOrdered.Length();
|
|
index < count;
|
|
index++) {
|
|
TransactionInfo* blockedInfo = mBlockingOrdered[index];
|
|
MOZ_ASSERT(blockedInfo);
|
|
|
|
blockedInfo->MaybeUnblock(this);
|
|
}
|
|
|
|
mBlocking.Clear();
|
|
mBlockingOrdered.Clear();
|
|
}
|
|
|
|
void
|
|
ConnectionPool::
|
|
TransactionInfo::MaybeUnblock(TransactionInfo* aTransactionInfo)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mBlockedOn.Contains(aTransactionInfo));
|
|
|
|
mBlockedOn.RemoveEntry(aTransactionInfo);
|
|
if (!mBlockedOn.Count()) {
|
|
MOZ_ASSERT(mDatabaseInfo);
|
|
|
|
ConnectionPool* connectionPool = mDatabaseInfo->mConnectionPool;
|
|
MOZ_ASSERT(connectionPool);
|
|
connectionPool->AssertIsOnOwningThread();
|
|
|
|
Unused <<
|
|
connectionPool->ScheduleTransaction(this,
|
|
/* aFromQueuedTransactions */ false);
|
|
}
|
|
}
|
|
|
|
ConnectionPool::
|
|
TransactionInfoPair::TransactionInfoPair()
|
|
: mLastBlockingReads(nullptr)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
MOZ_COUNT_CTOR(ConnectionPool::TransactionInfoPair);
|
|
}
|
|
|
|
ConnectionPool::
|
|
TransactionInfoPair::~TransactionInfoPair()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
MOZ_COUNT_DTOR(ConnectionPool::TransactionInfoPair);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Metadata classes
|
|
******************************************************************************/
|
|
|
|
bool
|
|
FullObjectStoreMetadata::HasLiveIndexes() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
for (auto iter = mIndexes.ConstIter(); !iter.Done(); iter.Next()) {
|
|
if (!iter.Data()->mDeleted) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
already_AddRefed<FullDatabaseMetadata>
|
|
FullDatabaseMetadata::Duplicate() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
// FullDatabaseMetadata contains two hash tables of pointers that we need to
|
|
// duplicate so we can't just use the copy constructor.
|
|
RefPtr<FullDatabaseMetadata> newMetadata =
|
|
new FullDatabaseMetadata(mCommonMetadata);
|
|
|
|
newMetadata->mDatabaseId = mDatabaseId;
|
|
newMetadata->mFilePath = mFilePath;
|
|
newMetadata->mNextObjectStoreId = mNextObjectStoreId;
|
|
newMetadata->mNextIndexId = mNextIndexId;
|
|
|
|
for (auto iter = mObjectStores.ConstIter(); !iter.Done(); iter.Next()) {
|
|
auto key = iter.Key();
|
|
auto value = iter.Data();
|
|
|
|
RefPtr<FullObjectStoreMetadata> newOSMetadata =
|
|
new FullObjectStoreMetadata();
|
|
|
|
newOSMetadata->mCommonMetadata = value->mCommonMetadata;
|
|
newOSMetadata->mNextAutoIncrementId = value->mNextAutoIncrementId;
|
|
newOSMetadata->mCommittedAutoIncrementId = value->mCommittedAutoIncrementId;
|
|
|
|
for (auto iter = value->mIndexes.ConstIter(); !iter.Done(); iter.Next()) {
|
|
auto key = iter.Key();
|
|
auto value = iter.Data();
|
|
|
|
RefPtr<FullIndexMetadata> newIndexMetadata = new FullIndexMetadata();
|
|
|
|
newIndexMetadata->mCommonMetadata = value->mCommonMetadata;
|
|
|
|
if (NS_WARN_IF(!newOSMetadata->mIndexes.Put(key, newIndexMetadata,
|
|
fallible))) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(value->mIndexes.Count() == newOSMetadata->mIndexes.Count());
|
|
|
|
if (NS_WARN_IF(!newMetadata->mObjectStores.Put(key, newOSMetadata,
|
|
fallible))) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(mObjectStores.Count() == newMetadata->mObjectStores.Count());
|
|
|
|
return newMetadata.forget();
|
|
}
|
|
|
|
DatabaseLoggingInfo::~DatabaseLoggingInfo()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (gLoggingInfoHashtable) {
|
|
const nsID& backgroundChildLoggingId =
|
|
mLoggingInfo.backgroundChildLoggingId();
|
|
|
|
MOZ_ASSERT(gLoggingInfoHashtable->Get(backgroundChildLoggingId) == this);
|
|
|
|
gLoggingInfoHashtable->Remove(backgroundChildLoggingId);
|
|
}
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Factory
|
|
******************************************************************************/
|
|
|
|
Factory::Factory(already_AddRefed<DatabaseLoggingInfo> aLoggingInfo)
|
|
: mLoggingInfo(Move(aLoggingInfo))
|
|
#ifdef DEBUG
|
|
, mActorDestroyed(false)
|
|
#endif
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
|
|
}
|
|
|
|
Factory::~Factory()
|
|
{
|
|
MOZ_ASSERT(mActorDestroyed);
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<Factory>
|
|
Factory::Create(const LoggingInfo& aLoggingInfo)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
|
|
|
|
// Balanced in ActoryDestroy().
|
|
IncreaseBusyCount();
|
|
|
|
MOZ_ASSERT(gLoggingInfoHashtable);
|
|
RefPtr<DatabaseLoggingInfo> loggingInfo =
|
|
gLoggingInfoHashtable->Get(aLoggingInfo.backgroundChildLoggingId());
|
|
if (loggingInfo) {
|
|
MOZ_ASSERT(aLoggingInfo.backgroundChildLoggingId() == loggingInfo->Id());
|
|
#if !DISABLE_ASSERTS_FOR_FUZZING
|
|
NS_WARNING_ASSERTION(
|
|
aLoggingInfo.nextTransactionSerialNumber() ==
|
|
loggingInfo->mLoggingInfo.nextTransactionSerialNumber(),
|
|
"NextTransactionSerialNumber doesn't match!");
|
|
NS_WARNING_ASSERTION(
|
|
aLoggingInfo.nextVersionChangeTransactionSerialNumber() ==
|
|
loggingInfo->mLoggingInfo.
|
|
nextVersionChangeTransactionSerialNumber(),
|
|
"NextVersionChangeTransactionSerialNumber doesn't match!");
|
|
NS_WARNING_ASSERTION(
|
|
aLoggingInfo.nextRequestSerialNumber() ==
|
|
loggingInfo->mLoggingInfo.nextRequestSerialNumber(),
|
|
"NextRequestSerialNumber doesn't match!");
|
|
#endif // !DISABLE_ASSERTS_FOR_FUZZING
|
|
} else {
|
|
loggingInfo = new DatabaseLoggingInfo(aLoggingInfo);
|
|
gLoggingInfoHashtable->Put(aLoggingInfo.backgroundChildLoggingId(),
|
|
loggingInfo);
|
|
}
|
|
|
|
RefPtr<Factory> actor = new Factory(loggingInfo.forget());
|
|
|
|
return actor.forget();
|
|
}
|
|
|
|
void
|
|
Factory::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
#ifdef DEBUG
|
|
mActorDestroyed = true;
|
|
#endif
|
|
|
|
// Match the IncreaseBusyCount in Create().
|
|
DecreaseBusyCount();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
Factory::RecvDeleteMe()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
IProtocol* mgr = Manager();
|
|
if (!PBackgroundIDBFactoryParent::Send__delete__(this)) {
|
|
return IPC_FAIL_NO_REASON(mgr);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
Factory::RecvIncrementLoggingRequestSerialNumber()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mLoggingInfo);
|
|
|
|
mLoggingInfo->NextRequestSN();
|
|
return IPC_OK();
|
|
}
|
|
|
|
PBackgroundIDBFactoryRequestParent*
|
|
Factory::AllocPBackgroundIDBFactoryRequestParent(
|
|
const FactoryRequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread())) {
|
|
return nullptr;
|
|
}
|
|
|
|
const CommonFactoryRequestParams* commonParams;
|
|
|
|
switch (aParams.type()) {
|
|
case FactoryRequestParams::TOpenDatabaseRequestParams: {
|
|
const OpenDatabaseRequestParams& params =
|
|
aParams.get_OpenDatabaseRequestParams();
|
|
commonParams = ¶ms.commonParams();
|
|
break;
|
|
}
|
|
|
|
case FactoryRequestParams::TDeleteDatabaseRequestParams: {
|
|
const DeleteDatabaseRequestParams& params =
|
|
aParams.get_DeleteDatabaseRequestParams();
|
|
commonParams = ¶ms.commonParams();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
MOZ_ASSERT(commonParams);
|
|
|
|
const DatabaseMetadata& metadata = commonParams->metadata();
|
|
if (NS_WARN_IF(metadata.persistenceType() != PERSISTENCE_TYPE_PERSISTENT &&
|
|
metadata.persistenceType() != PERSISTENCE_TYPE_TEMPORARY &&
|
|
metadata.persistenceType() != PERSISTENCE_TYPE_DEFAULT)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
|
|
const PrincipalInfo& principalInfo = commonParams->principalInfo();
|
|
if (NS_WARN_IF(principalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
|
|
if (NS_WARN_IF(principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo &&
|
|
metadata.persistenceType() != PERSISTENCE_TYPE_PERSISTENT)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<ContentParent> contentParent =
|
|
BackgroundParent::GetContentParent(Manager());
|
|
|
|
RefPtr<FactoryOp> actor;
|
|
if (aParams.type() == FactoryRequestParams::TOpenDatabaseRequestParams) {
|
|
actor = new OpenDatabaseOp(this,
|
|
contentParent.forget(),
|
|
*commonParams);
|
|
} else {
|
|
actor = new DeleteDatabaseOp(this, contentParent.forget(), *commonParams);
|
|
}
|
|
|
|
// Transfer ownership to IPDL.
|
|
return actor.forget().take();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
Factory::RecvPBackgroundIDBFactoryRequestConstructor(
|
|
PBackgroundIDBFactoryRequestParent* aActor,
|
|
const FactoryRequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(aParams.type() != FactoryRequestParams::T__None);
|
|
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
|
|
|
|
auto* op = static_cast<FactoryOp*>(aActor);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(op));
|
|
return IPC_OK();
|
|
}
|
|
|
|
bool
|
|
Factory::DeallocPBackgroundIDBFactoryRequestParent(
|
|
PBackgroundIDBFactoryRequestParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
// Transfer ownership back from IPDL.
|
|
RefPtr<FactoryOp> op = dont_AddRef(static_cast<FactoryOp*>(aActor));
|
|
return true;
|
|
}
|
|
|
|
PBackgroundIDBDatabaseParent*
|
|
Factory::AllocPBackgroundIDBDatabaseParent(
|
|
const DatabaseSpec& aSpec,
|
|
PBackgroundIDBFactoryRequestParent* aRequest)
|
|
{
|
|
MOZ_CRASH("PBackgroundIDBDatabaseParent actors should be constructed "
|
|
"manually!");
|
|
}
|
|
|
|
bool
|
|
Factory::DeallocPBackgroundIDBDatabaseParent(
|
|
PBackgroundIDBDatabaseParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
RefPtr<Database> database = dont_AddRef(static_cast<Database*>(aActor));
|
|
return true;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* WaitForTransactionsHelper
|
|
******************************************************************************/
|
|
|
|
void
|
|
WaitForTransactionsHelper::WaitForTransactions()
|
|
{
|
|
MOZ_ASSERT(mState == State::Initial);
|
|
|
|
Unused << this->Run();
|
|
}
|
|
|
|
void
|
|
WaitForTransactionsHelper::MaybeWaitForTransactions()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mState == State::Initial);
|
|
|
|
RefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
|
|
if (connectionPool) {
|
|
nsTArray<nsCString> ids(1);
|
|
ids.AppendElement(mDatabaseId);
|
|
|
|
mState = State::WaitingForTransactions;
|
|
|
|
connectionPool->WaitForDatabasesToComplete(Move(ids), this);
|
|
return;
|
|
}
|
|
|
|
MaybeWaitForFileHandles();
|
|
}
|
|
|
|
void
|
|
WaitForTransactionsHelper::MaybeWaitForFileHandles()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mState == State::Initial || mState == State::WaitingForTransactions);
|
|
|
|
RefPtr<FileHandleThreadPool> fileHandleThreadPool =
|
|
gFileHandleThreadPool.get();
|
|
if (fileHandleThreadPool) {
|
|
nsTArray<nsCString> ids(1);
|
|
ids.AppendElement(mDatabaseId);
|
|
|
|
mState = State::WaitingForFileHandles;
|
|
|
|
fileHandleThreadPool->WaitForDirectoriesToComplete(Move(ids), this);
|
|
return;
|
|
}
|
|
|
|
CallCallback();
|
|
}
|
|
|
|
void
|
|
WaitForTransactionsHelper::CallCallback()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mState == State::Initial ||
|
|
mState == State::WaitingForTransactions ||
|
|
mState == State::WaitingForFileHandles);
|
|
|
|
nsCOMPtr<nsIRunnable> callback;
|
|
mCallback.swap(callback);
|
|
|
|
callback->Run();
|
|
|
|
mState = State::Complete;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED0(WaitForTransactionsHelper, Runnable)
|
|
|
|
NS_IMETHODIMP
|
|
WaitForTransactionsHelper::Run()
|
|
{
|
|
MOZ_ASSERT(mState != State::Complete);
|
|
MOZ_ASSERT(mCallback);
|
|
|
|
switch (mState) {
|
|
case State::Initial:
|
|
MaybeWaitForTransactions();
|
|
break;
|
|
|
|
case State::WaitingForTransactions:
|
|
MaybeWaitForFileHandles();
|
|
break;
|
|
|
|
case State::WaitingForFileHandles:
|
|
CallCallback();
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Database
|
|
******************************************************************************/
|
|
|
|
Database::Database(Factory* aFactory,
|
|
const PrincipalInfo& aPrincipalInfo,
|
|
const Maybe<ContentParentId>& aOptionalContentParentId,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
uint32_t aTelemetryId,
|
|
FullDatabaseMetadata* aMetadata,
|
|
FileManager* aFileManager,
|
|
already_AddRefed<DirectoryLock> aDirectoryLock,
|
|
bool aFileHandleDisabled,
|
|
bool aChromeWriteAccessAllowed)
|
|
: mFactory(aFactory)
|
|
, mMetadata(aMetadata)
|
|
, mFileManager(aFileManager)
|
|
, mDirectoryLock(Move(aDirectoryLock))
|
|
, mPrincipalInfo(aPrincipalInfo)
|
|
, mOptionalContentParentId(aOptionalContentParentId)
|
|
, mGroup(aGroup)
|
|
, mOrigin(aOrigin)
|
|
, mId(aMetadata->mDatabaseId)
|
|
, mFilePath(aMetadata->mFilePath)
|
|
, mActiveMutableFileCount(0)
|
|
, mTelemetryId(aTelemetryId)
|
|
, mPersistenceType(aMetadata->mCommonMetadata.persistenceType())
|
|
, mFileHandleDisabled(aFileHandleDisabled)
|
|
, mChromeWriteAccessAllowed(aChromeWriteAccessAllowed)
|
|
, mClosed(false)
|
|
, mInvalidated(false)
|
|
, mActorWasAlive(false)
|
|
, mActorDestroyed(false)
|
|
, mMetadataCleanedUp(false)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aFactory);
|
|
MOZ_ASSERT(aMetadata);
|
|
MOZ_ASSERT(aFileManager);
|
|
MOZ_ASSERT_IF(aChromeWriteAccessAllowed,
|
|
aPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo);
|
|
}
|
|
|
|
void
|
|
Database::Invalidate()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
class MOZ_STACK_CLASS Helper final
|
|
{
|
|
public:
|
|
static bool
|
|
InvalidateTransactions(nsTHashtable<nsPtrHashKey<TransactionBase>>& aTable)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
const uint32_t count = aTable.Count();
|
|
if (!count) {
|
|
return true;
|
|
}
|
|
|
|
FallibleTArray<RefPtr<TransactionBase>> transactions;
|
|
if (NS_WARN_IF(!transactions.SetCapacity(count, fallible))) {
|
|
return false;
|
|
}
|
|
|
|
for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) {
|
|
if (NS_WARN_IF(!transactions.AppendElement(iter.Get()->GetKey(),
|
|
fallible))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (count) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
|
|
for (uint32_t index = 0; index < count; index++) {
|
|
RefPtr<TransactionBase> transaction = transactions[index].forget();
|
|
MOZ_ASSERT(transaction);
|
|
|
|
transaction->Invalidate();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
InvalidateMutableFiles(nsTHashtable<nsPtrHashKey<MutableFile>>& aTable)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
const uint32_t count = aTable.Count();
|
|
if (!count) {
|
|
return true;
|
|
}
|
|
|
|
FallibleTArray<RefPtr<MutableFile>> mutableFiles;
|
|
if (NS_WARN_IF(!mutableFiles.SetCapacity(count, fallible))) {
|
|
return false;
|
|
}
|
|
|
|
for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) {
|
|
if (NS_WARN_IF(!mutableFiles.AppendElement(iter.Get()->GetKey(),
|
|
fallible))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (count) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
|
|
for (uint32_t index = 0; index < count; index++) {
|
|
RefPtr<MutableFile> mutableFile = mutableFiles[index].forget();
|
|
MOZ_ASSERT(mutableFile);
|
|
|
|
mutableFile->Invalidate();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
if (mInvalidated) {
|
|
return;
|
|
}
|
|
|
|
mInvalidated = true;
|
|
|
|
if (mActorWasAlive && !mActorDestroyed) {
|
|
Unused << SendInvalidate();
|
|
}
|
|
|
|
if (!Helper::InvalidateTransactions(mTransactions)) {
|
|
NS_WARNING("Failed to abort all transactions!");
|
|
}
|
|
|
|
if (!Helper::InvalidateMutableFiles(mMutableFiles)) {
|
|
NS_WARNING("Failed to abort all mutable files!");
|
|
}
|
|
|
|
MOZ_ALWAYS_TRUE(CloseInternal());
|
|
|
|
CleanupMetadata();
|
|
}
|
|
|
|
nsresult
|
|
Database::EnsureConnection()
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"Database::EnsureConnection",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (!mConnection || !mConnection->GetStorageConnection()) {
|
|
nsresult rv =
|
|
gConnectionPool->GetOrCreateConnection(this, getter_AddRefs(mConnection));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
AssertIsOnConnectionThread();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
Database::RegisterTransaction(TransactionBase* aTransaction)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aTransaction);
|
|
MOZ_ASSERT(!mTransactions.GetEntry(aTransaction));
|
|
MOZ_ASSERT(mDirectoryLock);
|
|
MOZ_ASSERT(!mInvalidated);
|
|
MOZ_ASSERT(!mClosed);
|
|
|
|
if (NS_WARN_IF(!mTransactions.PutEntry(aTransaction, fallible))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Database::UnregisterTransaction(TransactionBase* aTransaction)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aTransaction);
|
|
MOZ_ASSERT(mTransactions.GetEntry(aTransaction));
|
|
|
|
mTransactions.RemoveEntry(aTransaction);
|
|
|
|
MaybeCloseConnection();
|
|
}
|
|
|
|
bool
|
|
Database::RegisterMutableFile(MutableFile* aMutableFile)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aMutableFile);
|
|
MOZ_ASSERT(!mMutableFiles.GetEntry(aMutableFile));
|
|
MOZ_ASSERT(mDirectoryLock);
|
|
|
|
if (NS_WARN_IF(!mMutableFiles.PutEntry(aMutableFile, fallible))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Database::UnregisterMutableFile(MutableFile* aMutableFile)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aMutableFile);
|
|
MOZ_ASSERT(mMutableFiles.GetEntry(aMutableFile));
|
|
|
|
mMutableFiles.RemoveEntry(aMutableFile);
|
|
}
|
|
|
|
void
|
|
Database::NoteActiveMutableFile()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mDirectoryLock);
|
|
MOZ_ASSERT(mActiveMutableFileCount < UINT32_MAX);
|
|
|
|
++mActiveMutableFileCount;
|
|
}
|
|
|
|
void
|
|
Database::NoteInactiveMutableFile()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mActiveMutableFileCount > 0);
|
|
|
|
--mActiveMutableFileCount;
|
|
|
|
MaybeCloseConnection();
|
|
}
|
|
|
|
void
|
|
Database::SetActorAlive()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorWasAlive);
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
mActorWasAlive = true;
|
|
|
|
// This reference will be absorbed by IPDL and released when the actor is
|
|
// destroyed.
|
|
AddRef();
|
|
}
|
|
|
|
bool
|
|
Database::CloseInternal()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (mClosed) {
|
|
if (NS_WARN_IF(!IsInvalidated())) {
|
|
// Kill misbehaving child for sending the close message twice.
|
|
return false;
|
|
}
|
|
|
|
// Ignore harmless race when we just invalidated the database.
|
|
return true;
|
|
}
|
|
|
|
mClosed = true;
|
|
|
|
if (gConnectionPool) {
|
|
gConnectionPool->CloseDatabaseWhenIdle(Id());
|
|
}
|
|
|
|
DatabaseActorInfo* info;
|
|
MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
|
|
|
|
MOZ_ASSERT(info->mLiveDatabases.Contains(this));
|
|
|
|
if (info->mWaitingFactoryOp) {
|
|
info->mWaitingFactoryOp->NoteDatabaseClosed(this);
|
|
}
|
|
|
|
MaybeCloseConnection();
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Database::MaybeCloseConnection()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (!mTransactions.Count() &&
|
|
!mActiveMutableFileCount &&
|
|
IsClosed() &&
|
|
mDirectoryLock) {
|
|
nsCOMPtr<nsIRunnable> callback =
|
|
NewRunnableMethod(this, &Database::ConnectionClosedCallback);
|
|
|
|
RefPtr<WaitForTransactionsHelper> helper =
|
|
new WaitForTransactionsHelper(Id(), callback);
|
|
helper->WaitForTransactions();
|
|
}
|
|
}
|
|
|
|
void
|
|
Database::ConnectionClosedCallback()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mClosed);
|
|
MOZ_ASSERT(!mTransactions.Count());
|
|
MOZ_ASSERT(!mActiveMutableFileCount);
|
|
|
|
mDirectoryLock = nullptr;
|
|
|
|
CleanupMetadata();
|
|
|
|
if (IsInvalidated() && IsActorAlive()) {
|
|
// Step 3 and 4 of "5.2 Closing a Database":
|
|
// 1. Wait for all transactions to complete.
|
|
// 2. Fire a close event if forced flag is set, i.e., IsInvalidated() in our
|
|
// implementation.
|
|
Unused << SendCloseAfterInvalidationComplete();
|
|
}
|
|
}
|
|
|
|
void
|
|
Database::CleanupMetadata()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (!mMetadataCleanedUp) {
|
|
mMetadataCleanedUp = true;
|
|
|
|
DatabaseActorInfo* info;
|
|
MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
|
|
MOZ_ALWAYS_TRUE(info->mLiveDatabases.RemoveElement(this));
|
|
|
|
if (info->mLiveDatabases.IsEmpty()) {
|
|
MOZ_ASSERT(!info->mWaitingFactoryOp ||
|
|
!info->mWaitingFactoryOp->HasBlockedDatabases());
|
|
gLiveDatabaseHashtable->Remove(Id());
|
|
}
|
|
|
|
// Match the IncreaseBusyCount in OpenDatabaseOp::EnsureDatabaseActor().
|
|
DecreaseBusyCount();
|
|
}
|
|
}
|
|
|
|
bool
|
|
Database::VerifyRequestParams(const DatabaseRequestParams& aParams) const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != DatabaseRequestParams::T__None);
|
|
|
|
switch (aParams.type()) {
|
|
case DatabaseRequestParams::TCreateFileParams: {
|
|
if (NS_WARN_IF(mFileHandleDisabled)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
const CreateFileParams& params = aParams.get_CreateFileParams();
|
|
|
|
if (NS_WARN_IF(params.name().IsEmpty())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Database::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
mActorDestroyed = true;
|
|
|
|
if (!IsInvalidated()) {
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
PBackgroundIDBDatabaseFileParent*
|
|
Database::AllocPBackgroundIDBDatabaseFileParent(PBlobParent* aBlobParent)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aBlobParent);
|
|
|
|
RefPtr<BlobImpl> blobImpl =
|
|
static_cast<BlobParent*>(aBlobParent)->GetBlobImpl();
|
|
MOZ_ASSERT(blobImpl);
|
|
|
|
RefPtr<FileInfo> fileInfo;
|
|
RefPtr<DatabaseFile> actor;
|
|
|
|
RefPtr<BlobImplStoredFile> storedFileImpl = do_QueryObject(blobImpl);
|
|
if (storedFileImpl && storedFileImpl->IsShareable(mFileManager)) {
|
|
// This blob was previously shared with the child.
|
|
fileInfo = storedFileImpl->GetFileInfo();
|
|
MOZ_ASSERT(fileInfo);
|
|
|
|
actor = new DatabaseFile(fileInfo);
|
|
} else {
|
|
// This is a blob we haven't seen before.
|
|
fileInfo = mFileManager->GetNewFileInfo();
|
|
MOZ_ASSERT(fileInfo);
|
|
|
|
actor = new DatabaseFile(blobImpl, fileInfo);
|
|
}
|
|
|
|
MOZ_ASSERT(actor);
|
|
|
|
return actor.forget().take();
|
|
}
|
|
|
|
bool
|
|
Database::DeallocPBackgroundIDBDatabaseFileParent(
|
|
PBackgroundIDBDatabaseFileParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
RefPtr<DatabaseFile> actor =
|
|
dont_AddRef(static_cast<DatabaseFile*>(aActor));
|
|
return true;
|
|
}
|
|
|
|
PBackgroundIDBDatabaseRequestParent*
|
|
Database::AllocPBackgroundIDBDatabaseRequestParent(
|
|
const DatabaseRequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != DatabaseRequestParams::T__None);
|
|
|
|
#ifdef DEBUG
|
|
// Always verify parameters in DEBUG builds!
|
|
bool trustParams = false;
|
|
#else
|
|
PBackgroundParent* backgroundActor = GetBackgroundParent();
|
|
MOZ_ASSERT(backgroundActor);
|
|
|
|
bool trustParams = !BackgroundParent::IsOtherProcessActor(backgroundActor);
|
|
#endif
|
|
|
|
if (NS_WARN_IF(!trustParams && !VerifyRequestParams(aParams))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<DatabaseOp> actor;
|
|
|
|
switch (aParams.type()) {
|
|
case DatabaseRequestParams::TCreateFileParams: {
|
|
actor = new CreateFileOp(this, aParams);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
MOZ_ASSERT(actor);
|
|
|
|
// Transfer ownership to IPDL.
|
|
return actor.forget().take();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
Database::RecvPBackgroundIDBDatabaseRequestConstructor(
|
|
PBackgroundIDBDatabaseRequestParent* aActor,
|
|
const DatabaseRequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(aParams.type() != DatabaseRequestParams::T__None);
|
|
|
|
auto* op = static_cast<DatabaseOp*>(aActor);
|
|
|
|
op->RunImmediately();
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
bool
|
|
Database::DeallocPBackgroundIDBDatabaseRequestParent(
|
|
PBackgroundIDBDatabaseRequestParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
// Transfer ownership back from IPDL.
|
|
RefPtr<DatabaseOp> op = dont_AddRef(static_cast<DatabaseOp*>(aActor));
|
|
return true;
|
|
}
|
|
|
|
PBackgroundIDBTransactionParent*
|
|
Database::AllocPBackgroundIDBTransactionParent(
|
|
const nsTArray<nsString>& aObjectStoreNames,
|
|
const Mode& aMode)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
// Once a database is closed it must not try to open new transactions.
|
|
if (NS_WARN_IF(mClosed)) {
|
|
if (!mInvalidated) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
if (NS_WARN_IF(aObjectStoreNames.IsEmpty())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
|
|
if (NS_WARN_IF(aMode != IDBTransaction::READ_ONLY &&
|
|
aMode != IDBTransaction::READ_WRITE &&
|
|
aMode != IDBTransaction::READ_WRITE_FLUSH &&
|
|
aMode != IDBTransaction::CLEANUP)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
|
|
// If this is a readwrite transaction to a chrome database make sure the child
|
|
// has write access.
|
|
if (NS_WARN_IF((aMode == IDBTransaction::READ_WRITE ||
|
|
aMode == IDBTransaction::READ_WRITE_FLUSH ||
|
|
aMode == IDBTransaction::CLEANUP) &&
|
|
mPrincipalInfo.type() == PrincipalInfo::TSystemPrincipalInfo &&
|
|
!mChromeWriteAccessAllowed)) {
|
|
return nullptr;
|
|
}
|
|
|
|
const ObjectStoreTable& objectStores = mMetadata->mObjectStores;
|
|
const uint32_t nameCount = aObjectStoreNames.Length();
|
|
|
|
if (NS_WARN_IF(nameCount > objectStores.Count())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
|
|
FallibleTArray<RefPtr<FullObjectStoreMetadata>> fallibleObjectStores;
|
|
if (NS_WARN_IF(!fallibleObjectStores.SetCapacity(nameCount, fallible))) {
|
|
return nullptr;
|
|
}
|
|
|
|
for (uint32_t nameIndex = 0; nameIndex < nameCount; nameIndex++) {
|
|
const nsString& name = aObjectStoreNames[nameIndex];
|
|
|
|
if (nameIndex) {
|
|
// Make sure that this name is sorted properly and not a duplicate.
|
|
if (NS_WARN_IF(name <= aObjectStoreNames[nameIndex - 1])) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
for (auto iter = objectStores.ConstIter(); !iter.Done(); iter.Next()) {
|
|
auto value = iter.Data();
|
|
MOZ_ASSERT(iter.Key());
|
|
|
|
if (name == value->mCommonMetadata.name() && !value->mDeleted) {
|
|
if (NS_WARN_IF(!fallibleObjectStores.AppendElement(value, fallible))) {
|
|
return nullptr;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
nsTArray<RefPtr<FullObjectStoreMetadata>> infallibleObjectStores;
|
|
infallibleObjectStores.SwapElements(fallibleObjectStores);
|
|
|
|
RefPtr<NormalTransaction> transaction =
|
|
new NormalTransaction(this, aMode, infallibleObjectStores);
|
|
|
|
MOZ_ASSERT(infallibleObjectStores.IsEmpty());
|
|
|
|
return transaction.forget().take();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
Database::RecvPBackgroundIDBTransactionConstructor(
|
|
PBackgroundIDBTransactionParent* aActor,
|
|
InfallibleTArray<nsString>&& aObjectStoreNames,
|
|
const Mode& aMode)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
|
|
MOZ_ASSERT(aMode == IDBTransaction::READ_ONLY ||
|
|
aMode == IDBTransaction::READ_WRITE ||
|
|
aMode == IDBTransaction::READ_WRITE_FLUSH ||
|
|
aMode == IDBTransaction::CLEANUP);
|
|
MOZ_ASSERT(!mClosed);
|
|
|
|
if (IsInvalidated()) {
|
|
// This is an expected race. We don't want the child to die here, just don't
|
|
// actually do any work.
|
|
return IPC_OK();
|
|
}
|
|
|
|
if (!gConnectionPool) {
|
|
gConnectionPool = new ConnectionPool();
|
|
}
|
|
|
|
auto* transaction = static_cast<NormalTransaction*>(aActor);
|
|
|
|
RefPtr<StartTransactionOp> startOp = new StartTransactionOp(transaction);
|
|
|
|
uint64_t transactionId =
|
|
startOp->StartOnConnectionPool(GetLoggingInfo()->Id(),
|
|
mMetadata->mDatabaseId,
|
|
transaction->LoggingSerialNumber(),
|
|
aObjectStoreNames,
|
|
aMode != IDBTransaction::READ_ONLY);
|
|
|
|
transaction->SetActive(transactionId);
|
|
|
|
if (NS_WARN_IF(!RegisterTransaction(transaction))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
transaction->Abort(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, /* aForce */ false);
|
|
return IPC_OK();
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
bool
|
|
Database::DeallocPBackgroundIDBTransactionParent(
|
|
PBackgroundIDBTransactionParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
RefPtr<NormalTransaction> transaction =
|
|
dont_AddRef(static_cast<NormalTransaction*>(aActor));
|
|
return true;
|
|
}
|
|
|
|
PBackgroundIDBVersionChangeTransactionParent*
|
|
Database::AllocPBackgroundIDBVersionChangeTransactionParent(
|
|
const uint64_t& aCurrentVersion,
|
|
const uint64_t& aRequestedVersion,
|
|
const int64_t& aNextObjectStoreId,
|
|
const int64_t& aNextIndexId)
|
|
{
|
|
MOZ_CRASH("PBackgroundIDBVersionChangeTransactionParent actors should be "
|
|
"constructed manually!");
|
|
}
|
|
|
|
bool
|
|
Database::DeallocPBackgroundIDBVersionChangeTransactionParent(
|
|
PBackgroundIDBVersionChangeTransactionParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
RefPtr<VersionChangeTransaction> transaction =
|
|
dont_AddRef(static_cast<VersionChangeTransaction*>(aActor));
|
|
return true;
|
|
}
|
|
|
|
Database::PBackgroundMutableFileParent*
|
|
Database::AllocPBackgroundMutableFileParent(const nsString& aName,
|
|
const nsString& aType)
|
|
{
|
|
MOZ_CRASH("PBackgroundMutableFileParent actors should be constructed "
|
|
"manually!");
|
|
}
|
|
|
|
bool
|
|
Database::DeallocPBackgroundMutableFileParent(
|
|
PBackgroundMutableFileParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
// Transfer ownership back from IPDL.
|
|
RefPtr<MutableFile> mutableFile =
|
|
dont_AddRef(static_cast<MutableFile*>(aActor));
|
|
return true;
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
Database::RecvDeleteMe()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
IProtocol* mgr = Manager();
|
|
if (!PBackgroundIDBDatabaseParent::Send__delete__(this)) {
|
|
return IPC_FAIL_NO_REASON(mgr);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
Database::RecvBlocked()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(mClosed)) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
DatabaseActorInfo* info;
|
|
MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
|
|
|
|
MOZ_ASSERT(info->mLiveDatabases.Contains(this));
|
|
MOZ_ASSERT(info->mWaitingFactoryOp);
|
|
|
|
info->mWaitingFactoryOp->NoteDatabaseBlocked(this);
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
Database::RecvClose()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!CloseInternal())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
void
|
|
Database::
|
|
StartTransactionOp::RunOnConnectionThread()
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(Transaction());
|
|
MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
|
|
|
|
IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld]: "
|
|
"Beginning database work",
|
|
"IndexedDB %s: P T[%lld]: DB Start",
|
|
IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
|
|
mLoggingSerialNumber);
|
|
|
|
TransactionDatabaseOperationBase::RunOnConnectionThread();
|
|
}
|
|
|
|
nsresult
|
|
Database::
|
|
StartTransactionOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
|
|
Transaction()->SetActiveOnConnectionThread();
|
|
|
|
if (Transaction()->GetMode() == IDBTransaction::CLEANUP) {
|
|
nsresult rv = aConnection->DisableQuotaChecks();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (Transaction()->GetMode() != IDBTransaction::READ_ONLY) {
|
|
nsresult rv = aConnection->BeginWriteTransaction();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Database::
|
|
StartTransactionOp::SendSuccessResult()
|
|
{
|
|
// We don't need to do anything here.
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
Database::
|
|
StartTransactionOp::SendFailureResult(nsresult /* aResultCode */)
|
|
{
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
|
|
// Abort the transaction.
|
|
return false;
|
|
}
|
|
|
|
void
|
|
Database::
|
|
StartTransactionOp::Cleanup()
|
|
{
|
|
#ifdef DEBUG
|
|
// StartTransactionOp is not a normal database operation that is tied to an
|
|
// actor. Do this to make our assertions happy.
|
|
NoteActorDestroyed();
|
|
#endif
|
|
|
|
TransactionDatabaseOperationBase::Cleanup();
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* TransactionBase
|
|
******************************************************************************/
|
|
|
|
TransactionBase::TransactionBase(Database* aDatabase, Mode aMode)
|
|
: mDatabase(aDatabase)
|
|
, mTransactionId(0)
|
|
, mDatabaseId(aDatabase->Id())
|
|
, mLoggingSerialNumber(aDatabase->GetLoggingInfo()->NextTransactionSN(aMode))
|
|
, mActiveRequestCount(0)
|
|
, mInvalidatedOnAnyThread(false)
|
|
, mMode(aMode)
|
|
, mHasBeenActive(false)
|
|
, mHasBeenActiveOnConnectionThread(false)
|
|
, mActorDestroyed(false)
|
|
, mInvalidated(false)
|
|
, mResultCode(NS_OK)
|
|
, mCommitOrAbortReceived(false)
|
|
, mCommittedOrAborted(false)
|
|
, mForceAborted(false)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aDatabase);
|
|
MOZ_ASSERT(mLoggingSerialNumber);
|
|
}
|
|
|
|
TransactionBase::~TransactionBase()
|
|
{
|
|
MOZ_ASSERT(!mActiveRequestCount);
|
|
MOZ_ASSERT(mActorDestroyed);
|
|
MOZ_ASSERT_IF(mHasBeenActive, mCommittedOrAborted);
|
|
}
|
|
|
|
void
|
|
TransactionBase::Abort(nsresult aResultCode, bool aForce)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(NS_FAILED(aResultCode));
|
|
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = aResultCode;
|
|
}
|
|
|
|
if (aForce) {
|
|
mForceAborted = true;
|
|
}
|
|
|
|
MaybeCommitOrAbort();
|
|
}
|
|
|
|
bool
|
|
TransactionBase::RecvCommit()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
mCommitOrAbortReceived = true;
|
|
|
|
MaybeCommitOrAbort();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
TransactionBase::RecvAbort(nsresult aResultCode)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(NS_SUCCEEDED(aResultCode))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_ERROR_GET_MODULE(aResultCode) !=
|
|
NS_ERROR_MODULE_DOM_INDEXEDDB)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
mCommitOrAbortReceived = true;
|
|
|
|
Abort(aResultCode, /* aForce */ false);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
TransactionBase::CommitOrAbort()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mCommittedOrAborted);
|
|
|
|
mCommittedOrAborted = true;
|
|
|
|
if (!mHasBeenActive) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<CommitOp> commitOp =
|
|
new CommitOp(this, ClampResultCode(mResultCode));
|
|
|
|
gConnectionPool->Finish(TransactionId(), commitOp);
|
|
}
|
|
|
|
already_AddRefed<FullObjectStoreMetadata>
|
|
TransactionBase::GetMetadataForObjectStoreId(int64_t aObjectStoreId) const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aObjectStoreId);
|
|
|
|
if (!aObjectStoreId) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> metadata;
|
|
if (!mDatabase->Metadata()->mObjectStores.Get(aObjectStoreId,
|
|
getter_AddRefs(metadata)) ||
|
|
metadata->mDeleted) {
|
|
return nullptr;
|
|
}
|
|
|
|
MOZ_ASSERT(metadata->mCommonMetadata.id() == aObjectStoreId);
|
|
|
|
return metadata.forget();
|
|
}
|
|
|
|
already_AddRefed<FullIndexMetadata>
|
|
TransactionBase::GetMetadataForIndexId(
|
|
FullObjectStoreMetadata* const aObjectStoreMetadata,
|
|
int64_t aIndexId) const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aIndexId);
|
|
|
|
if (!aIndexId) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<FullIndexMetadata> metadata;
|
|
if (!aObjectStoreMetadata->mIndexes.Get(aIndexId, getter_AddRefs(metadata)) ||
|
|
metadata->mDeleted) {
|
|
return nullptr;
|
|
}
|
|
|
|
MOZ_ASSERT(metadata->mCommonMetadata.id() == aIndexId);
|
|
|
|
return metadata.forget();
|
|
}
|
|
|
|
void
|
|
TransactionBase::NoteModifiedAutoIncrementObjectStore(
|
|
FullObjectStoreMetadata* aMetadata)
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aMetadata);
|
|
|
|
if (!mModifiedAutoIncrementObjectStoreMetadataArray.Contains(aMetadata)) {
|
|
mModifiedAutoIncrementObjectStoreMetadataArray.AppendElement(aMetadata);
|
|
}
|
|
}
|
|
|
|
void
|
|
TransactionBase::ForgetModifiedAutoIncrementObjectStore(
|
|
FullObjectStoreMetadata* aMetadata)
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aMetadata);
|
|
|
|
mModifiedAutoIncrementObjectStoreMetadataArray.RemoveElement(aMetadata);
|
|
}
|
|
|
|
bool
|
|
TransactionBase::VerifyRequestParams(const RequestParams& aParams) const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != RequestParams::T__None);
|
|
|
|
switch (aParams.type()) {
|
|
case RequestParams::TObjectStoreAddParams: {
|
|
const ObjectStoreAddPutParams& params =
|
|
aParams.get_ObjectStoreAddParams().commonParams();
|
|
if (NS_WARN_IF(!VerifyRequestParams(params))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TObjectStorePutParams: {
|
|
const ObjectStoreAddPutParams& params =
|
|
aParams.get_ObjectStorePutParams().commonParams();
|
|
if (NS_WARN_IF(!VerifyRequestParams(params))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TObjectStoreGetParams: {
|
|
const ObjectStoreGetParams& params = aParams.get_ObjectStoreGetParams();
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TObjectStoreGetKeyParams: {
|
|
const ObjectStoreGetKeyParams& params =
|
|
aParams.get_ObjectStoreGetKeyParams();
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TObjectStoreGetAllParams: {
|
|
const ObjectStoreGetAllParams& params =
|
|
aParams.get_ObjectStoreGetAllParams();
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TObjectStoreGetAllKeysParams: {
|
|
const ObjectStoreGetAllKeysParams& params =
|
|
aParams.get_ObjectStoreGetAllKeysParams();
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TObjectStoreDeleteParams: {
|
|
if (NS_WARN_IF(mMode != IDBTransaction::READ_WRITE &&
|
|
mMode != IDBTransaction::READ_WRITE_FLUSH &&
|
|
mMode != IDBTransaction::CLEANUP &&
|
|
mMode != IDBTransaction::VERSION_CHANGE)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
const ObjectStoreDeleteParams& params =
|
|
aParams.get_ObjectStoreDeleteParams();
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TObjectStoreClearParams: {
|
|
if (NS_WARN_IF(mMode != IDBTransaction::READ_WRITE &&
|
|
mMode != IDBTransaction::READ_WRITE_FLUSH &&
|
|
mMode != IDBTransaction::CLEANUP &&
|
|
mMode != IDBTransaction::VERSION_CHANGE)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
const ObjectStoreClearParams& params =
|
|
aParams.get_ObjectStoreClearParams();
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TObjectStoreCountParams: {
|
|
const ObjectStoreCountParams& params =
|
|
aParams.get_ObjectStoreCountParams();
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
|
|
case RequestParams::TIndexGetParams: {
|
|
const IndexGetParams& params = aParams.get_IndexGetParams();
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
const RefPtr<FullIndexMetadata> indexMetadata =
|
|
GetMetadataForIndexId(objectStoreMetadata, params.indexId());
|
|
if (NS_WARN_IF(!indexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TIndexGetKeyParams: {
|
|
const IndexGetKeyParams& params = aParams.get_IndexGetKeyParams();
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
const RefPtr<FullIndexMetadata> indexMetadata =
|
|
GetMetadataForIndexId(objectStoreMetadata, params.indexId());
|
|
if (NS_WARN_IF(!indexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!VerifyRequestParams(params.keyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TIndexGetAllParams: {
|
|
const IndexGetAllParams& params = aParams.get_IndexGetAllParams();
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
const RefPtr<FullIndexMetadata> indexMetadata =
|
|
GetMetadataForIndexId(objectStoreMetadata, params.indexId());
|
|
if (NS_WARN_IF(!indexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TIndexGetAllKeysParams: {
|
|
const IndexGetAllKeysParams& params = aParams.get_IndexGetAllKeysParams();
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
const RefPtr<FullIndexMetadata> indexMetadata =
|
|
GetMetadataForIndexId(objectStoreMetadata, params.indexId());
|
|
if (NS_WARN_IF(!indexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TIndexCountParams: {
|
|
const IndexCountParams& params = aParams.get_IndexCountParams();
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
const RefPtr<FullIndexMetadata> indexMetadata =
|
|
GetMetadataForIndexId(objectStoreMetadata, params.indexId());
|
|
if (NS_WARN_IF(!indexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
TransactionBase::VerifyRequestParams(const SerializedKeyRange& aParams) const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
// XXX Check more here?
|
|
|
|
if (aParams.isOnly()) {
|
|
if (NS_WARN_IF(aParams.lower().IsUnset())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!aParams.upper().IsUnset())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(aParams.lowerOpen())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(aParams.upperOpen())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
} else if (NS_WARN_IF(aParams.lower().IsUnset() &&
|
|
aParams.upper().IsUnset())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
TransactionBase::VerifyRequestParams(const ObjectStoreAddPutParams& aParams)
|
|
const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(mMode != IDBTransaction::READ_WRITE &&
|
|
mMode != IDBTransaction::READ_WRITE_FLUSH &&
|
|
mMode != IDBTransaction::VERSION_CHANGE)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> objMetadata =
|
|
GetMetadataForObjectStoreId(aParams.objectStoreId());
|
|
if (NS_WARN_IF(!objMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!aParams.cloneInfo().data().data.Size())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (objMetadata->mCommonMetadata.autoIncrement() &&
|
|
objMetadata->mCommonMetadata.keyPath().IsValid() &&
|
|
aParams.key().IsUnset()) {
|
|
const SerializedStructuredCloneWriteInfo cloneInfo = aParams.cloneInfo();
|
|
|
|
if (NS_WARN_IF(!cloneInfo.offsetToKeyProp())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(cloneInfo.data().data.Size() < sizeof(uint64_t))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(cloneInfo.offsetToKeyProp() >
|
|
(cloneInfo.data().data.Size() - sizeof(uint64_t)))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
} else if (NS_WARN_IF(aParams.cloneInfo().offsetToKeyProp())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
const nsTArray<IndexUpdateInfo>& updates = aParams.indexUpdateInfos();
|
|
|
|
for (uint32_t index = 0; index < updates.Length(); index++) {
|
|
RefPtr<FullIndexMetadata> indexMetadata =
|
|
GetMetadataForIndexId(objMetadata, updates[index].indexId());
|
|
if (NS_WARN_IF(!indexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(updates[index].value().IsUnset())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
MOZ_ASSERT(!updates[index].value().GetBuffer().IsEmpty());
|
|
}
|
|
|
|
const nsTArray<FileAddInfo>& fileAddInfos = aParams.fileAddInfos();
|
|
|
|
for (uint32_t index = 0; index < fileAddInfos.Length(); index++) {
|
|
const FileAddInfo& fileAddInfo = fileAddInfos[index];
|
|
|
|
const DatabaseOrMutableFile& file = fileAddInfo.file();
|
|
MOZ_ASSERT(file.type() != DatabaseOrMutableFile::T__None);
|
|
|
|
switch (fileAddInfo.type()) {
|
|
case StructuredCloneFile::eBlob:
|
|
if (NS_WARN_IF(file.type() !=
|
|
DatabaseOrMutableFile::TPBackgroundIDBDatabaseFileParent)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!file.get_PBackgroundIDBDatabaseFileParent())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case StructuredCloneFile::eMutableFile: {
|
|
if (NS_WARN_IF(file.type() !=
|
|
DatabaseOrMutableFile::TPBackgroundMutableFileParent)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(mDatabase->IsFileHandleDisabled())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
auto mutableFile =
|
|
static_cast<MutableFile*>(file.get_PBackgroundMutableFileParent());
|
|
|
|
if (NS_WARN_IF(!mutableFile)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
Database* database = mutableFile->GetDatabase();
|
|
if (NS_WARN_IF(!database)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(database->Id() != mDatabase->Id())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case StructuredCloneFile::eStructuredClone:
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
|
|
case StructuredCloneFile::eWasmBytecode:
|
|
case StructuredCloneFile::eWasmCompiled:
|
|
if (NS_WARN_IF(file.type() !=
|
|
DatabaseOrMutableFile::TPBackgroundIDBDatabaseFileParent)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
if (NS_WARN_IF(!file.get_PBackgroundIDBDatabaseFileParent())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case StructuredCloneFile::eEndGuard:
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
TransactionBase::VerifyRequestParams(const OptionalKeyRange& aParams) const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != OptionalKeyRange::T__None);
|
|
|
|
switch (aParams.type()) {
|
|
case OptionalKeyRange::TSerializedKeyRange:
|
|
if (NS_WARN_IF(!VerifyRequestParams(aParams.get_SerializedKeyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case OptionalKeyRange::Tvoid_t:
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
TransactionBase::NoteActiveRequest()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mActiveRequestCount < UINT64_MAX);
|
|
|
|
mActiveRequestCount++;
|
|
}
|
|
|
|
void
|
|
TransactionBase::NoteFinishedRequest()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mActiveRequestCount);
|
|
|
|
mActiveRequestCount--;
|
|
|
|
MaybeCommitOrAbort();
|
|
}
|
|
|
|
void
|
|
TransactionBase::Invalidate()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mInvalidated == mInvalidatedOnAnyThread);
|
|
|
|
if (!mInvalidated) {
|
|
mInvalidated = true;
|
|
mInvalidatedOnAnyThread = true;
|
|
|
|
Abort(NS_ERROR_DOM_INDEXEDDB_ABORT_ERR, /* aForce */ false);
|
|
}
|
|
}
|
|
|
|
PBackgroundIDBRequestParent*
|
|
TransactionBase::AllocRequest(const RequestParams& aParams, bool aTrustParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != RequestParams::T__None);
|
|
|
|
#ifdef DEBUG
|
|
// Always verify parameters in DEBUG builds!
|
|
aTrustParams = false;
|
|
#endif
|
|
|
|
if (!aTrustParams && NS_WARN_IF(!VerifyRequestParams(aParams))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<NormalTransactionOp> actor;
|
|
|
|
switch (aParams.type()) {
|
|
case RequestParams::TObjectStoreAddParams:
|
|
case RequestParams::TObjectStorePutParams:
|
|
actor = new ObjectStoreAddOrPutRequestOp(this, aParams);
|
|
break;
|
|
|
|
case RequestParams::TObjectStoreGetParams:
|
|
actor =
|
|
new ObjectStoreGetRequestOp(this, aParams, /* aGetAll */ false);
|
|
break;
|
|
|
|
case RequestParams::TObjectStoreGetAllParams:
|
|
actor =
|
|
new ObjectStoreGetRequestOp(this, aParams, /* aGetAll */ true);
|
|
break;
|
|
|
|
case RequestParams::TObjectStoreGetKeyParams:
|
|
actor =
|
|
new ObjectStoreGetKeyRequestOp(this, aParams, /* aGetAll */ false);
|
|
break;
|
|
|
|
case RequestParams::TObjectStoreGetAllKeysParams:
|
|
actor =
|
|
new ObjectStoreGetKeyRequestOp(this, aParams, /* aGetAll */ true);
|
|
break;
|
|
|
|
case RequestParams::TObjectStoreDeleteParams:
|
|
actor =
|
|
new ObjectStoreDeleteRequestOp(this,
|
|
aParams.get_ObjectStoreDeleteParams());
|
|
break;
|
|
|
|
case RequestParams::TObjectStoreClearParams:
|
|
actor =
|
|
new ObjectStoreClearRequestOp(this,
|
|
aParams.get_ObjectStoreClearParams());
|
|
break;
|
|
|
|
case RequestParams::TObjectStoreCountParams:
|
|
actor =
|
|
new ObjectStoreCountRequestOp(this,
|
|
aParams.get_ObjectStoreCountParams());
|
|
break;
|
|
|
|
case RequestParams::TIndexGetParams:
|
|
actor = new IndexGetRequestOp(this, aParams, /* aGetAll */ false);
|
|
break;
|
|
|
|
case RequestParams::TIndexGetKeyParams:
|
|
actor = new IndexGetKeyRequestOp(this, aParams, /* aGetAll */ false);
|
|
break;
|
|
|
|
case RequestParams::TIndexGetAllParams:
|
|
actor = new IndexGetRequestOp(this, aParams, /* aGetAll */ true);
|
|
break;
|
|
|
|
case RequestParams::TIndexGetAllKeysParams:
|
|
actor = new IndexGetKeyRequestOp(this, aParams, /* aGetAll */ true);
|
|
break;
|
|
|
|
case RequestParams::TIndexCountParams:
|
|
actor = new IndexCountRequestOp(this, aParams);
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
MOZ_ASSERT(actor);
|
|
|
|
// Transfer ownership to IPDL.
|
|
return actor.forget().take();
|
|
}
|
|
|
|
bool
|
|
TransactionBase::StartRequest(PBackgroundIDBRequestParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
auto* op = static_cast<NormalTransactionOp*>(aActor);
|
|
|
|
if (NS_WARN_IF(!op->Init(this))) {
|
|
op->Cleanup();
|
|
return false;
|
|
}
|
|
|
|
op->DispatchToConnectionPool();
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
TransactionBase::DeallocRequest(PBackgroundIDBRequestParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
// Transfer ownership back from IPDL.
|
|
RefPtr<NormalTransactionOp> actor =
|
|
dont_AddRef(static_cast<NormalTransactionOp*>(aActor));
|
|
return true;
|
|
}
|
|
|
|
PBackgroundIDBCursorParent*
|
|
TransactionBase::AllocCursor(const OpenCursorParams& aParams, bool aTrustParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
|
|
|
|
#ifdef DEBUG
|
|
// Always verify parameters in DEBUG builds!
|
|
aTrustParams = false;
|
|
#endif
|
|
|
|
OpenCursorParams::Type type = aParams.type();
|
|
RefPtr<FullObjectStoreMetadata> objectStoreMetadata;
|
|
RefPtr<FullIndexMetadata> indexMetadata;
|
|
Cursor::Direction direction;
|
|
|
|
switch (type) {
|
|
case OpenCursorParams::TObjectStoreOpenCursorParams: {
|
|
const ObjectStoreOpenCursorParams& params =
|
|
aParams.get_ObjectStoreOpenCursorParams();
|
|
objectStoreMetadata = GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
if (aTrustParams &&
|
|
NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
direction = params.direction();
|
|
break;
|
|
}
|
|
|
|
case OpenCursorParams::TObjectStoreOpenKeyCursorParams: {
|
|
const ObjectStoreOpenKeyCursorParams& params =
|
|
aParams.get_ObjectStoreOpenKeyCursorParams();
|
|
objectStoreMetadata = GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
if (aTrustParams &&
|
|
NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
direction = params.direction();
|
|
break;
|
|
}
|
|
|
|
case OpenCursorParams::TIndexOpenCursorParams: {
|
|
const IndexOpenCursorParams& params = aParams.get_IndexOpenCursorParams();
|
|
objectStoreMetadata = GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
indexMetadata =
|
|
GetMetadataForIndexId(objectStoreMetadata, params.indexId());
|
|
if (NS_WARN_IF(!indexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
if (aTrustParams &&
|
|
NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
direction = params.direction();
|
|
break;
|
|
}
|
|
|
|
case OpenCursorParams::TIndexOpenKeyCursorParams: {
|
|
const IndexOpenKeyCursorParams& params =
|
|
aParams.get_IndexOpenKeyCursorParams();
|
|
objectStoreMetadata = GetMetadataForObjectStoreId(params.objectStoreId());
|
|
if (NS_WARN_IF(!objectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
indexMetadata =
|
|
GetMetadataForIndexId(objectStoreMetadata, params.indexId());
|
|
if (NS_WARN_IF(!indexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
if (aTrustParams &&
|
|
NS_WARN_IF(!VerifyRequestParams(params.optionalKeyRange()))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
direction = params.direction();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<Cursor> actor =
|
|
new Cursor(this, type, objectStoreMetadata, indexMetadata, direction);
|
|
|
|
// Transfer ownership to IPDL.
|
|
return actor.forget().take();
|
|
}
|
|
|
|
bool
|
|
TransactionBase::StartCursor(PBackgroundIDBCursorParent* aActor,
|
|
const OpenCursorParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
|
|
|
|
auto* op = static_cast<Cursor*>(aActor);
|
|
|
|
if (NS_WARN_IF(!op->Start(aParams))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
TransactionBase::DeallocCursor(PBackgroundIDBCursorParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
// Transfer ownership back from IPDL.
|
|
RefPtr<Cursor> actor = dont_AddRef(static_cast<Cursor*>(aActor));
|
|
return true;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* NormalTransaction
|
|
******************************************************************************/
|
|
|
|
NormalTransaction::NormalTransaction(
|
|
Database* aDatabase,
|
|
TransactionBase::Mode aMode,
|
|
nsTArray<RefPtr<FullObjectStoreMetadata>>& aObjectStores)
|
|
: TransactionBase(aDatabase, aMode)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!aObjectStores.IsEmpty());
|
|
|
|
mObjectStores.SwapElements(aObjectStores);
|
|
}
|
|
|
|
bool
|
|
NormalTransaction::IsSameProcessActor()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
PBackgroundParent* actor = Manager()->Manager()->Manager();
|
|
MOZ_ASSERT(actor);
|
|
|
|
return !BackgroundParent::IsOtherProcessActor(actor);
|
|
}
|
|
|
|
void
|
|
NormalTransaction::SendCompleteNotification(nsresult aResult)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (!IsActorDestroyed()) {
|
|
Unused << SendComplete(aResult);
|
|
}
|
|
}
|
|
|
|
void
|
|
NormalTransaction::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
NoteActorDestroyed();
|
|
|
|
if (!mCommittedOrAborted) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
mForceAborted = true;
|
|
|
|
MaybeCommitOrAbort();
|
|
}
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
NormalTransaction::RecvDeleteMe()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
IProtocol* mgr = Manager();
|
|
if (!PBackgroundIDBTransactionParent::Send__delete__(this)) {
|
|
return IPC_FAIL_NO_REASON(mgr);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
NormalTransaction::RecvCommit()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (!TransactionBase::RecvCommit()) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
NormalTransaction::RecvAbort(const nsresult& aResultCode)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (!TransactionBase::RecvAbort(aResultCode)) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
PBackgroundIDBRequestParent*
|
|
NormalTransaction::AllocPBackgroundIDBRequestParent(
|
|
const RequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != RequestParams::T__None);
|
|
|
|
return AllocRequest(aParams, IsSameProcessActor());
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
NormalTransaction::RecvPBackgroundIDBRequestConstructor(
|
|
PBackgroundIDBRequestParent* aActor,
|
|
const RequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(aParams.type() != RequestParams::T__None);
|
|
|
|
if (!StartRequest(aActor)) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
bool
|
|
NormalTransaction::DeallocPBackgroundIDBRequestParent(
|
|
PBackgroundIDBRequestParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
return DeallocRequest(aActor);
|
|
}
|
|
|
|
PBackgroundIDBCursorParent*
|
|
NormalTransaction::AllocPBackgroundIDBCursorParent(
|
|
const OpenCursorParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return AllocCursor(aParams, IsSameProcessActor());
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
NormalTransaction::RecvPBackgroundIDBCursorConstructor(
|
|
PBackgroundIDBCursorParent* aActor,
|
|
const OpenCursorParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
|
|
|
|
if (!StartCursor(aActor, aParams)) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
bool
|
|
NormalTransaction::DeallocPBackgroundIDBCursorParent(
|
|
PBackgroundIDBCursorParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
return DeallocCursor(aActor);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* VersionChangeTransaction
|
|
******************************************************************************/
|
|
|
|
VersionChangeTransaction::VersionChangeTransaction(
|
|
OpenDatabaseOp* aOpenDatabaseOp)
|
|
: TransactionBase(aOpenDatabaseOp->mDatabase,
|
|
IDBTransaction::VERSION_CHANGE)
|
|
, mOpenDatabaseOp(aOpenDatabaseOp)
|
|
, mActorWasAlive(false)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aOpenDatabaseOp);
|
|
}
|
|
|
|
VersionChangeTransaction::~VersionChangeTransaction()
|
|
{
|
|
#ifdef DEBUG
|
|
// Silence the base class' destructor assertion if we never made this actor
|
|
// live.
|
|
FakeActorDestroyed();
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::IsSameProcessActor()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
PBackgroundParent* actor = Manager()->Manager()->Manager();
|
|
MOZ_ASSERT(actor);
|
|
|
|
return !BackgroundParent::IsOtherProcessActor(actor);
|
|
}
|
|
|
|
void
|
|
VersionChangeTransaction::SetActorAlive()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorWasAlive);
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
mActorWasAlive = true;
|
|
|
|
// This reference will be absorbed by IPDL and released when the actor is
|
|
// destroyed.
|
|
AddRef();
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::CopyDatabaseMetadata()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mOldMetadata);
|
|
|
|
const RefPtr<FullDatabaseMetadata> origMetadata =
|
|
GetDatabase()->Metadata();
|
|
MOZ_ASSERT(origMetadata);
|
|
|
|
RefPtr<FullDatabaseMetadata> newMetadata = origMetadata->Duplicate();
|
|
if (NS_WARN_IF(!newMetadata)) {
|
|
return false;
|
|
}
|
|
|
|
// Replace the live metadata with the new mutable copy.
|
|
DatabaseActorInfo* info;
|
|
MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(origMetadata->mDatabaseId,
|
|
&info));
|
|
MOZ_ASSERT(!info->mLiveDatabases.IsEmpty());
|
|
MOZ_ASSERT(info->mMetadata == origMetadata);
|
|
|
|
mOldMetadata = info->mMetadata.forget();
|
|
info->mMetadata.swap(newMetadata);
|
|
|
|
// Replace metadata pointers for all live databases.
|
|
for (uint32_t count = info->mLiveDatabases.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
info->mLiveDatabases[index]->mMetadata = info->mMetadata;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
VersionChangeTransaction::UpdateMetadata(nsresult aResult)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(GetDatabase());
|
|
MOZ_ASSERT(mOpenDatabaseOp);
|
|
MOZ_ASSERT(!!mActorWasAlive == !!mOpenDatabaseOp->mDatabase);
|
|
MOZ_ASSERT_IF(mActorWasAlive, !mOpenDatabaseOp->mDatabaseId.IsEmpty());
|
|
|
|
if (IsActorDestroyed() || !mActorWasAlive) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<FullDatabaseMetadata> oldMetadata;
|
|
mOldMetadata.swap(oldMetadata);
|
|
|
|
DatabaseActorInfo* info;
|
|
if (!gLiveDatabaseHashtable->Get(oldMetadata->mDatabaseId, &info)) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(!info->mLiveDatabases.IsEmpty());
|
|
|
|
if (NS_SUCCEEDED(aResult)) {
|
|
// Remove all deleted objectStores and indexes, then mark immutable.
|
|
for (auto objectStoreIter = info->mMetadata->mObjectStores.Iter();
|
|
!objectStoreIter.Done();
|
|
objectStoreIter.Next()) {
|
|
MOZ_ASSERT(objectStoreIter.Key());
|
|
RefPtr<FullObjectStoreMetadata>& metadata = objectStoreIter.Data();
|
|
MOZ_ASSERT(metadata);
|
|
|
|
if (metadata->mDeleted) {
|
|
objectStoreIter.Remove();
|
|
continue;
|
|
}
|
|
|
|
for (auto indexIter = metadata->mIndexes.Iter();
|
|
!indexIter.Done();
|
|
indexIter.Next()) {
|
|
MOZ_ASSERT(indexIter.Key());
|
|
RefPtr<FullIndexMetadata>& index = indexIter.Data();
|
|
MOZ_ASSERT(index);
|
|
|
|
if (index->mDeleted) {
|
|
indexIter.Remove();
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
metadata->mIndexes.MarkImmutable();
|
|
#endif
|
|
}
|
|
#ifdef DEBUG
|
|
info->mMetadata->mObjectStores.MarkImmutable();
|
|
#endif
|
|
} else {
|
|
// Replace metadata pointers for all live databases.
|
|
info->mMetadata = oldMetadata.forget();
|
|
|
|
for (uint32_t count = info->mLiveDatabases.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
info->mLiveDatabases[index]->mMetadata = info->mMetadata;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
VersionChangeTransaction::SendCompleteNotification(nsresult aResult)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mOpenDatabaseOp);
|
|
MOZ_ASSERT_IF(!mActorWasAlive, NS_FAILED(mOpenDatabaseOp->mResultCode));
|
|
MOZ_ASSERT_IF(!mActorWasAlive,
|
|
mOpenDatabaseOp->mState > OpenDatabaseOp::State::SendingResults);
|
|
|
|
RefPtr<OpenDatabaseOp> openDatabaseOp;
|
|
mOpenDatabaseOp.swap(openDatabaseOp);
|
|
|
|
if (!mActorWasAlive) {
|
|
return;
|
|
}
|
|
|
|
if (NS_FAILED(aResult) && NS_SUCCEEDED(openDatabaseOp->mResultCode)) {
|
|
// 3.3.1 Opening a database:
|
|
// "If the upgrade transaction was aborted, run the steps for closing a
|
|
// database connection with connection, create and return a new AbortError
|
|
// exception and abort these steps."
|
|
openDatabaseOp->mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
|
|
}
|
|
|
|
openDatabaseOp->mState = OpenDatabaseOp::State::SendingResults;
|
|
|
|
if (!IsActorDestroyed()) {
|
|
Unused << SendComplete(aResult);
|
|
}
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(openDatabaseOp->Run());
|
|
}
|
|
|
|
void
|
|
VersionChangeTransaction::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
NoteActorDestroyed();
|
|
|
|
if (!mCommittedOrAborted) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
mForceAborted = true;
|
|
|
|
MaybeCommitOrAbort();
|
|
}
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
VersionChangeTransaction::RecvDeleteMe()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
IProtocol* mgr = Manager();
|
|
if (!PBackgroundIDBVersionChangeTransactionParent::Send__delete__(this)) {
|
|
return IPC_FAIL_NO_REASON(mgr);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
VersionChangeTransaction::RecvCommit()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (!TransactionBase::RecvCommit()) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
VersionChangeTransaction::RecvAbort(const nsresult& aResultCode)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (!TransactionBase::RecvAbort(aResultCode)) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
VersionChangeTransaction::RecvCreateObjectStore(
|
|
const ObjectStoreMetadata& aMetadata)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!aMetadata.id())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
|
|
MOZ_ASSERT(dbMetadata);
|
|
|
|
if (NS_WARN_IF(aMetadata.id() != dbMetadata->mNextObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
auto* foundMetadata =
|
|
MetadataNameOrIdMatcher<FullObjectStoreMetadata>::Match(
|
|
dbMetadata->mObjectStores, aMetadata.id(), aMetadata.name());
|
|
|
|
if (NS_WARN_IF(foundMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> newMetadata = new FullObjectStoreMetadata();
|
|
newMetadata->mCommonMetadata = aMetadata;
|
|
newMetadata->mNextAutoIncrementId = aMetadata.autoIncrement() ? 1 : 0;
|
|
newMetadata->mCommittedAutoIncrementId = newMetadata->mNextAutoIncrementId;
|
|
|
|
if (NS_WARN_IF(!dbMetadata->mObjectStores.Put(aMetadata.id(), newMetadata,
|
|
fallible))) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
dbMetadata->mNextObjectStoreId++;
|
|
|
|
RefPtr<CreateObjectStoreOp> op = new CreateObjectStoreOp(this, aMetadata);
|
|
|
|
if (NS_WARN_IF(!op->Init(this))) {
|
|
op->Cleanup();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
op->DispatchToConnectionPool();
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
VersionChangeTransaction::RecvDeleteObjectStore(const int64_t& aObjectStoreId)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!aObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
|
|
MOZ_ASSERT(dbMetadata);
|
|
MOZ_ASSERT(dbMetadata->mNextObjectStoreId > 0);
|
|
|
|
if (NS_WARN_IF(aObjectStoreId >= dbMetadata->mNextObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> foundMetadata =
|
|
GetMetadataForObjectStoreId(aObjectStoreId);
|
|
|
|
if (NS_WARN_IF(!foundMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
foundMetadata->mDeleted = true;
|
|
|
|
bool isLastObjectStore = true;
|
|
DebugOnly<bool> foundTargetId = false;
|
|
for (auto iter = dbMetadata->mObjectStores.Iter();
|
|
!iter.Done();
|
|
iter.Next()) {
|
|
if (uint64_t(aObjectStoreId) == iter.Key()) {
|
|
foundTargetId = true;
|
|
} else if (!iter.UserData()->mDeleted) {
|
|
isLastObjectStore = false;
|
|
break;
|
|
}
|
|
}
|
|
MOZ_ASSERT_IF(isLastObjectStore, foundTargetId);
|
|
|
|
RefPtr<DeleteObjectStoreOp> op =
|
|
new DeleteObjectStoreOp(this, foundMetadata, isLastObjectStore);
|
|
|
|
if (NS_WARN_IF(!op->Init(this))) {
|
|
op->Cleanup();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
op->DispatchToConnectionPool();
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
VersionChangeTransaction::RecvRenameObjectStore(const int64_t& aObjectStoreId,
|
|
const nsString& aName)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!aObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
|
|
MOZ_ASSERT(dbMetadata);
|
|
MOZ_ASSERT(dbMetadata->mNextObjectStoreId > 0);
|
|
|
|
if (NS_WARN_IF(aObjectStoreId >= dbMetadata->mNextObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> foundMetadata =
|
|
GetMetadataForObjectStoreId(aObjectStoreId);
|
|
|
|
if (NS_WARN_IF(!foundMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
foundMetadata->mCommonMetadata.name() = aName;
|
|
|
|
RefPtr<RenameObjectStoreOp> renameOp =
|
|
new RenameObjectStoreOp(this, foundMetadata);
|
|
|
|
if (NS_WARN_IF(!renameOp->Init(this))) {
|
|
renameOp->Cleanup();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
renameOp->DispatchToConnectionPool();
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
VersionChangeTransaction::RecvCreateIndex(const int64_t& aObjectStoreId,
|
|
const IndexMetadata& aMetadata)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!aObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(!aMetadata.id())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
|
|
MOZ_ASSERT(dbMetadata);
|
|
|
|
if (NS_WARN_IF(aMetadata.id() != dbMetadata->mNextIndexId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
|
|
GetMetadataForObjectStoreId(aObjectStoreId);
|
|
|
|
if (NS_WARN_IF(!foundObjectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
RefPtr<FullIndexMetadata> foundIndexMetadata =
|
|
MetadataNameOrIdMatcher<FullIndexMetadata>::Match(
|
|
foundObjectStoreMetadata->mIndexes, aMetadata.id(), aMetadata.name());
|
|
|
|
if (NS_WARN_IF(foundIndexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
RefPtr<FullIndexMetadata> newMetadata = new FullIndexMetadata();
|
|
newMetadata->mCommonMetadata = aMetadata;
|
|
|
|
if (NS_WARN_IF(!foundObjectStoreMetadata->mIndexes.Put(aMetadata.id(),
|
|
newMetadata,
|
|
fallible))) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
dbMetadata->mNextIndexId++;
|
|
|
|
RefPtr<CreateIndexOp> op =
|
|
new CreateIndexOp(this, aObjectStoreId, aMetadata);
|
|
|
|
if (NS_WARN_IF(!op->Init(this))) {
|
|
op->Cleanup();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
op->DispatchToConnectionPool();
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
VersionChangeTransaction::RecvDeleteIndex(const int64_t& aObjectStoreId,
|
|
const int64_t& aIndexId)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!aObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(!aIndexId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
|
|
MOZ_ASSERT(dbMetadata);
|
|
MOZ_ASSERT(dbMetadata->mNextObjectStoreId > 0);
|
|
MOZ_ASSERT(dbMetadata->mNextIndexId > 0);
|
|
|
|
if (NS_WARN_IF(aObjectStoreId >= dbMetadata->mNextObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(aIndexId >= dbMetadata->mNextIndexId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
|
|
GetMetadataForObjectStoreId(aObjectStoreId);
|
|
|
|
if (NS_WARN_IF(!foundObjectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
RefPtr<FullIndexMetadata> foundIndexMetadata =
|
|
GetMetadataForIndexId(foundObjectStoreMetadata, aIndexId);
|
|
|
|
if (NS_WARN_IF(!foundIndexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
foundIndexMetadata->mDeleted = true;
|
|
|
|
bool isLastIndex = true;
|
|
DebugOnly<bool> foundTargetId = false;
|
|
for (auto iter = foundObjectStoreMetadata->mIndexes.ConstIter();
|
|
!iter.Done();
|
|
iter.Next()) {
|
|
if (uint64_t(aIndexId) == iter.Key()) {
|
|
foundTargetId = true;
|
|
} else if (!iter.UserData()->mDeleted) {
|
|
isLastIndex = false;
|
|
break;
|
|
}
|
|
}
|
|
MOZ_ASSERT_IF(isLastIndex, foundTargetId);
|
|
|
|
RefPtr<DeleteIndexOp> op =
|
|
new DeleteIndexOp(this,
|
|
aObjectStoreId,
|
|
aIndexId,
|
|
foundIndexMetadata->mCommonMetadata.unique(),
|
|
isLastIndex);
|
|
|
|
if (NS_WARN_IF(!op->Init(this))) {
|
|
op->Cleanup();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
op->DispatchToConnectionPool();
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
VersionChangeTransaction::RecvRenameIndex(const int64_t& aObjectStoreId,
|
|
const int64_t& aIndexId,
|
|
const nsString& aName)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!aObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(!aIndexId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
|
|
MOZ_ASSERT(dbMetadata);
|
|
MOZ_ASSERT(dbMetadata->mNextObjectStoreId > 0);
|
|
MOZ_ASSERT(dbMetadata->mNextIndexId > 0);
|
|
|
|
if (NS_WARN_IF(aObjectStoreId >= dbMetadata->mNextObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(aIndexId >= dbMetadata->mNextIndexId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
|
|
GetMetadataForObjectStoreId(aObjectStoreId);
|
|
|
|
if (NS_WARN_IF(!foundObjectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
RefPtr<FullIndexMetadata> foundIndexMetadata =
|
|
GetMetadataForIndexId(foundObjectStoreMetadata, aIndexId);
|
|
|
|
if (NS_WARN_IF(!foundIndexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
foundIndexMetadata->mCommonMetadata.name() = aName;
|
|
|
|
RefPtr<RenameIndexOp> renameOp =
|
|
new RenameIndexOp(this, foundIndexMetadata, aObjectStoreId);
|
|
|
|
if (NS_WARN_IF(!renameOp->Init(this))) {
|
|
renameOp->Cleanup();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
renameOp->DispatchToConnectionPool();
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
PBackgroundIDBRequestParent*
|
|
VersionChangeTransaction::AllocPBackgroundIDBRequestParent(
|
|
const RequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != RequestParams::T__None);
|
|
|
|
return AllocRequest(aParams, IsSameProcessActor());
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
VersionChangeTransaction::RecvPBackgroundIDBRequestConstructor(
|
|
PBackgroundIDBRequestParent* aActor,
|
|
const RequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(aParams.type() != RequestParams::T__None);
|
|
|
|
if (!StartRequest(aActor)) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::DeallocPBackgroundIDBRequestParent(
|
|
PBackgroundIDBRequestParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
return DeallocRequest(aActor);
|
|
}
|
|
|
|
PBackgroundIDBCursorParent*
|
|
VersionChangeTransaction::AllocPBackgroundIDBCursorParent(
|
|
const OpenCursorParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return AllocCursor(aParams, IsSameProcessActor());
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
VersionChangeTransaction::RecvPBackgroundIDBCursorConstructor(
|
|
PBackgroundIDBCursorParent* aActor,
|
|
const OpenCursorParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
|
|
|
|
if (!StartCursor(aActor, aParams)) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::DeallocPBackgroundIDBCursorParent(
|
|
PBackgroundIDBCursorParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
return DeallocCursor(aActor);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Cursor
|
|
******************************************************************************/
|
|
|
|
Cursor::Cursor(TransactionBase* aTransaction,
|
|
Type aType,
|
|
FullObjectStoreMetadata* aObjectStoreMetadata,
|
|
FullIndexMetadata* aIndexMetadata,
|
|
Direction aDirection)
|
|
: mTransaction(aTransaction)
|
|
, mBackgroundParent(nullptr)
|
|
, mObjectStoreMetadata(aObjectStoreMetadata)
|
|
, mIndexMetadata(aIndexMetadata)
|
|
, mObjectStoreId(aObjectStoreMetadata->mCommonMetadata.id())
|
|
, mIndexId(aIndexMetadata ? aIndexMetadata->mCommonMetadata.id() : 0)
|
|
, mCurrentlyRunningOp(nullptr)
|
|
, mType(aType)
|
|
, mDirection(aDirection)
|
|
, mUniqueIndex(aIndexMetadata ?
|
|
aIndexMetadata->mCommonMetadata.unique() :
|
|
false)
|
|
, mIsSameProcessActor(!BackgroundParent::IsOtherProcessActor(
|
|
aTransaction->GetBackgroundParent()))
|
|
, mActorDestroyed(false)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aTransaction);
|
|
MOZ_ASSERT(aType != OpenCursorParams::T__None);
|
|
MOZ_ASSERT(aObjectStoreMetadata);
|
|
MOZ_ASSERT_IF(aType == OpenCursorParams::TIndexOpenCursorParams ||
|
|
aType == OpenCursorParams::TIndexOpenKeyCursorParams,
|
|
aIndexMetadata);
|
|
|
|
if (mType == OpenCursorParams::TObjectStoreOpenCursorParams ||
|
|
mType == OpenCursorParams::TIndexOpenCursorParams) {
|
|
mDatabase = aTransaction->GetDatabase();
|
|
MOZ_ASSERT(mDatabase);
|
|
|
|
mFileManager = mDatabase->GetFileManager();
|
|
MOZ_ASSERT(mFileManager);
|
|
|
|
mBackgroundParent = aTransaction->GetBackgroundParent();
|
|
MOZ_ASSERT(mBackgroundParent);
|
|
}
|
|
|
|
if (aIndexMetadata) {
|
|
mLocale = aIndexMetadata->mCommonMetadata.locale();
|
|
}
|
|
|
|
static_assert(OpenCursorParams::T__None == 0 &&
|
|
OpenCursorParams::T__Last == 4,
|
|
"Lots of code here assumes only four types of cursors!");
|
|
}
|
|
|
|
bool
|
|
Cursor::VerifyRequestParams(const CursorRequestParams& aParams) const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None);
|
|
MOZ_ASSERT(mObjectStoreMetadata);
|
|
MOZ_ASSERT_IF(mType == OpenCursorParams::TIndexOpenCursorParams ||
|
|
mType == OpenCursorParams::TIndexOpenKeyCursorParams,
|
|
mIndexMetadata);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
mTransaction->GetMetadataForObjectStoreId(mObjectStoreId);
|
|
if (objectStoreMetadata) {
|
|
MOZ_ASSERT(objectStoreMetadata == mObjectStoreMetadata);
|
|
} else {
|
|
MOZ_ASSERT(mObjectStoreMetadata->mDeleted);
|
|
}
|
|
|
|
if (objectStoreMetadata &&
|
|
(mType == OpenCursorParams::TIndexOpenCursorParams ||
|
|
mType == OpenCursorParams::TIndexOpenKeyCursorParams)) {
|
|
RefPtr<FullIndexMetadata> indexMetadata =
|
|
mTransaction->GetMetadataForIndexId(objectStoreMetadata, mIndexId);
|
|
if (indexMetadata) {
|
|
MOZ_ASSERT(indexMetadata == mIndexMetadata);
|
|
} else {
|
|
MOZ_ASSERT(mIndexMetadata->mDeleted);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (NS_WARN_IF(mObjectStoreMetadata->mDeleted) ||
|
|
(mIndexMetadata && NS_WARN_IF(mIndexMetadata->mDeleted))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
const Key& sortKey = IsLocaleAware() ? mSortKey : mKey;
|
|
|
|
switch (aParams.type()) {
|
|
case CursorRequestParams::TContinueParams: {
|
|
const Key& key = aParams.get_ContinueParams().key();
|
|
if (!key.IsUnset()) {
|
|
switch (mDirection) {
|
|
case IDBCursor::NEXT:
|
|
case IDBCursor::NEXT_UNIQUE:
|
|
if (NS_WARN_IF(key <= sortKey)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case IDBCursor::PREV:
|
|
case IDBCursor::PREV_UNIQUE:
|
|
if (NS_WARN_IF(key >= sortKey)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CursorRequestParams::TContinuePrimaryKeyParams: {
|
|
const Key& key = aParams.get_ContinuePrimaryKeyParams().key();
|
|
const Key& primaryKey = aParams.get_ContinuePrimaryKeyParams().primaryKey();
|
|
MOZ_ASSERT(!key.IsUnset());
|
|
MOZ_ASSERT(!primaryKey.IsUnset());
|
|
switch (mDirection) {
|
|
case IDBCursor::NEXT:
|
|
if (NS_WARN_IF(key < sortKey ||
|
|
(key == sortKey && primaryKey <= mObjectKey))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case IDBCursor::PREV:
|
|
if (NS_WARN_IF(key > sortKey ||
|
|
(key == sortKey && primaryKey >= mObjectKey))) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case CursorRequestParams::TAdvanceParams:
|
|
if (NS_WARN_IF(!aParams.get_AdvanceParams().count())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Cursor::Start(const OpenCursorParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() == mType);
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
if (NS_WARN_IF(mCurrentlyRunningOp)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
const OptionalKeyRange& optionalKeyRange =
|
|
mType == OpenCursorParams::TObjectStoreOpenCursorParams ?
|
|
aParams.get_ObjectStoreOpenCursorParams().optionalKeyRange() :
|
|
mType == OpenCursorParams::TObjectStoreOpenKeyCursorParams ?
|
|
aParams.get_ObjectStoreOpenKeyCursorParams().optionalKeyRange() :
|
|
mType == OpenCursorParams::TIndexOpenCursorParams ?
|
|
aParams.get_IndexOpenCursorParams().optionalKeyRange() :
|
|
aParams.get_IndexOpenKeyCursorParams().optionalKeyRange();
|
|
|
|
RefPtr<OpenOp> openOp = new OpenOp(this, optionalKeyRange);
|
|
|
|
if (NS_WARN_IF(!openOp->Init(mTransaction))) {
|
|
openOp->Cleanup();
|
|
return false;
|
|
}
|
|
|
|
openOp->DispatchToConnectionPool();
|
|
mCurrentlyRunningOp = openOp;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Cursor::SendResponseInternal(
|
|
CursorResponse& aResponse,
|
|
const nsTArray<FallibleTArray<StructuredCloneFile>>& aFiles)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aResponse.type() != CursorResponse::T__None);
|
|
MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tnsresult,
|
|
NS_FAILED(aResponse.get_nsresult()));
|
|
MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tnsresult,
|
|
NS_ERROR_GET_MODULE(aResponse.get_nsresult()) ==
|
|
NS_ERROR_MODULE_DOM_INDEXEDDB);
|
|
MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tvoid_t, mKey.IsUnset());
|
|
MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tvoid_t,
|
|
mRangeKey.IsUnset());
|
|
MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tvoid_t,
|
|
mObjectKey.IsUnset());
|
|
MOZ_ASSERT_IF(aResponse.type() == CursorResponse::Tnsresult ||
|
|
aResponse.type() == CursorResponse::Tvoid_t ||
|
|
aResponse.type() ==
|
|
CursorResponse::TObjectStoreKeyCursorResponse ||
|
|
aResponse.type() == CursorResponse::TIndexKeyCursorResponse,
|
|
aFiles.IsEmpty());
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
MOZ_ASSERT(mCurrentlyRunningOp);
|
|
|
|
for (size_t i = 0; i < aFiles.Length(); ++i) {
|
|
const auto& files = aFiles[i];
|
|
if (!files.IsEmpty()) {
|
|
MOZ_ASSERT(aResponse.type() ==
|
|
CursorResponse::TArrayOfObjectStoreCursorResponse ||
|
|
aResponse.type() == CursorResponse::TIndexCursorResponse);
|
|
MOZ_ASSERT(mDatabase);
|
|
MOZ_ASSERT(mBackgroundParent);
|
|
|
|
FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
|
|
nsresult rv = SerializeStructuredCloneFiles(mBackgroundParent,
|
|
mDatabase,
|
|
files,
|
|
/* aForPreprocess */ false,
|
|
serializedFiles);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
aResponse = ClampResultCode(rv);
|
|
break;
|
|
}
|
|
|
|
SerializedStructuredCloneReadInfo* serializedInfo = nullptr;
|
|
switch (aResponse.type()) {
|
|
case CursorResponse::TArrayOfObjectStoreCursorResponse: {
|
|
auto& responses = aResponse.get_ArrayOfObjectStoreCursorResponse();
|
|
MOZ_ASSERT(i < responses.Length());
|
|
serializedInfo = &responses[i].cloneInfo();
|
|
break;
|
|
}
|
|
|
|
case CursorResponse::TIndexCursorResponse:
|
|
MOZ_ASSERT(i == 0);
|
|
serializedInfo = &aResponse.get_IndexCursorResponse().cloneInfo();
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
MOZ_ASSERT(serializedInfo);
|
|
MOZ_ASSERT(serializedInfo->files().IsEmpty());
|
|
|
|
serializedInfo->files().SwapElements(serializedFiles);
|
|
}
|
|
}
|
|
|
|
// Work around the deleted function by casting to the base class.
|
|
auto* base = static_cast<PBackgroundIDBCursorParent*>(this);
|
|
if (!base->SendResponse(aResponse)) {
|
|
NS_WARNING("Failed to send response!");
|
|
}
|
|
|
|
mCurrentlyRunningOp = nullptr;
|
|
}
|
|
|
|
void
|
|
Cursor::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
mActorDestroyed = true;
|
|
|
|
if (mCurrentlyRunningOp) {
|
|
mCurrentlyRunningOp->NoteActorDestroyed();
|
|
}
|
|
|
|
mBackgroundParent = nullptr;
|
|
|
|
mObjectStoreMetadata = nullptr;
|
|
mIndexMetadata = nullptr;
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
Cursor::RecvDeleteMe()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
if (NS_WARN_IF(mCurrentlyRunningOp)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
IProtocol* mgr = Manager();
|
|
if (!PBackgroundIDBCursorParent::Send__delete__(this)) {
|
|
return IPC_FAIL_NO_REASON(mgr);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
Cursor::RecvContinue(const CursorRequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != CursorRequestParams::T__None);
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
MOZ_ASSERT(mObjectStoreMetadata);
|
|
MOZ_ASSERT_IF(mType == OpenCursorParams::TIndexOpenCursorParams ||
|
|
mType == OpenCursorParams::TIndexOpenKeyCursorParams,
|
|
mIndexMetadata);
|
|
|
|
const bool trustParams =
|
|
#ifdef DEBUG
|
|
// Always verify parameters in DEBUG builds!
|
|
false
|
|
#else
|
|
mIsSameProcessActor
|
|
#endif
|
|
;
|
|
|
|
if (!trustParams && !VerifyRequestParams(aParams)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(mCurrentlyRunningOp)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(mTransaction->mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
RefPtr<ContinueOp> continueOp = new ContinueOp(this, aParams);
|
|
if (NS_WARN_IF(!continueOp->Init(mTransaction))) {
|
|
continueOp->Cleanup();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
continueOp->DispatchToConnectionPool();
|
|
mCurrentlyRunningOp = continueOp;
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* FileManager
|
|
******************************************************************************/
|
|
|
|
FileManager::FileManager(PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
bool aIsApp,
|
|
const nsAString& aDatabaseName,
|
|
bool aEnforcingQuota)
|
|
: mPersistenceType(aPersistenceType)
|
|
, mGroup(aGroup)
|
|
, mOrigin(aOrigin)
|
|
, mDatabaseName(aDatabaseName)
|
|
, mLastFileId(0)
|
|
, mIsApp(aIsApp)
|
|
, mEnforcingQuota(aEnforcingQuota)
|
|
, mInvalidated(false)
|
|
{ }
|
|
|
|
nsresult
|
|
FileManager::Init(nsIFile* aDirectory,
|
|
mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aDirectory);
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
bool exists;
|
|
nsresult rv = aDirectory->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (exists) {
|
|
bool isDirectory;
|
|
rv = aDirectory->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!isDirectory)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
} else {
|
|
rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = aDirectory->GetPath(mDirectoryPath);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> journalDirectory;
|
|
rv = aDirectory->Clone(getter_AddRefs(journalDirectory));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = journalDirectory->Append(NS_LITERAL_STRING(JOURNAL_DIRECTORY_NAME));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = journalDirectory->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (exists) {
|
|
bool isDirectory;
|
|
rv = journalDirectory->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!isDirectory)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
rv = journalDirectory->GetPath(mJournalDirectoryPath);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT id, refcount "
|
|
"FROM file"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool hasResult;
|
|
while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
|
|
int64_t id;
|
|
rv = stmt->GetInt64(0, &id);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
int32_t refcount;
|
|
rv = stmt->GetInt32(1, &refcount);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(refcount > 0);
|
|
|
|
RefPtr<FileInfo> fileInfo = FileInfo::Create(this, id);
|
|
fileInfo->mDBRefCnt = static_cast<nsrefcnt>(refcount);
|
|
|
|
mFileInfos.Put(id, fileInfo);
|
|
|
|
mLastFileId = std::max(id, mLastFileId);
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
FileManager::Invalidate()
|
|
{
|
|
if (IndexedDatabaseManager::IsClosed()) {
|
|
MOZ_ASSERT(false, "Shouldn't be called after shutdown!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
|
|
|
|
MOZ_ASSERT(!mInvalidated);
|
|
mInvalidated = true;
|
|
|
|
for (auto iter = mFileInfos.Iter(); !iter.Done(); iter.Next()) {
|
|
FileInfo* info = iter.Data();
|
|
MOZ_ASSERT(info);
|
|
|
|
if (!info->LockedClearDBRefs()) {
|
|
iter.Remove();
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<nsIFile>
|
|
FileManager::GetDirectory()
|
|
{
|
|
return GetFileForPath(mDirectoryPath);
|
|
}
|
|
|
|
already_AddRefed<nsIFile>
|
|
FileManager::GetCheckedDirectory()
|
|
{
|
|
nsCOMPtr<nsIFile> directory = GetDirectory();
|
|
if (NS_WARN_IF(!directory)) {
|
|
return nullptr;
|
|
}
|
|
|
|
DebugOnly<bool> exists;
|
|
MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)));
|
|
MOZ_ASSERT(exists);
|
|
|
|
DebugOnly<bool> isDirectory;
|
|
MOZ_ASSERT(NS_SUCCEEDED(directory->IsDirectory(&isDirectory)));
|
|
MOZ_ASSERT(isDirectory);
|
|
|
|
return directory.forget();
|
|
}
|
|
|
|
already_AddRefed<nsIFile>
|
|
FileManager::GetJournalDirectory()
|
|
{
|
|
return GetFileForPath(mJournalDirectoryPath);
|
|
}
|
|
|
|
already_AddRefed<nsIFile>
|
|
FileManager::EnsureJournalDirectory()
|
|
{
|
|
// This can happen on the IO or on a transaction thread.
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsIFile> journalDirectory = GetFileForPath(mJournalDirectoryPath);
|
|
if (NS_WARN_IF(!journalDirectory)) {
|
|
return nullptr;
|
|
}
|
|
|
|
bool exists;
|
|
nsresult rv = journalDirectory->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (exists) {
|
|
bool isDirectory;
|
|
rv = journalDirectory->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (NS_WARN_IF(!isDirectory)) {
|
|
return nullptr;
|
|
}
|
|
} else {
|
|
rv = journalDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return journalDirectory.forget();
|
|
}
|
|
|
|
already_AddRefed<FileInfo>
|
|
FileManager::GetFileInfo(int64_t aId)
|
|
{
|
|
if (IndexedDatabaseManager::IsClosed()) {
|
|
MOZ_ASSERT(false, "Shouldn't be called after shutdown!");
|
|
return nullptr;
|
|
}
|
|
|
|
FileInfo* fileInfo;
|
|
{
|
|
MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
|
|
fileInfo = mFileInfos.Get(aId);
|
|
}
|
|
|
|
RefPtr<FileInfo> result = fileInfo;
|
|
return result.forget();
|
|
}
|
|
|
|
already_AddRefed<FileInfo>
|
|
FileManager::GetNewFileInfo()
|
|
{
|
|
MOZ_ASSERT(!IndexedDatabaseManager::IsClosed());
|
|
|
|
FileInfo* fileInfo;
|
|
{
|
|
MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
|
|
|
|
int64_t id = mLastFileId + 1;
|
|
|
|
fileInfo = FileInfo::Create(this, id);
|
|
|
|
mFileInfos.Put(id, fileInfo);
|
|
|
|
mLastFileId = id;
|
|
}
|
|
|
|
RefPtr<FileInfo> result = fileInfo;
|
|
return result.forget();
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<nsIFile>
|
|
FileManager::GetFileForId(nsIFile* aDirectory, int64_t aId)
|
|
{
|
|
MOZ_ASSERT(aDirectory);
|
|
MOZ_ASSERT(aId > 0);
|
|
|
|
nsAutoString id;
|
|
id.AppendInt(aId);
|
|
|
|
nsCOMPtr<nsIFile> file;
|
|
nsresult rv = aDirectory->Clone(getter_AddRefs(file));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
|
|
rv = file->Append(id);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
|
|
return file.forget();
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<nsIFile>
|
|
FileManager::GetCheckedFileForId(nsIFile* aDirectory, int64_t aId)
|
|
{
|
|
nsCOMPtr<nsIFile> file = GetFileForId(aDirectory, aId);
|
|
if (NS_WARN_IF(!file)) {
|
|
return nullptr;
|
|
}
|
|
|
|
DebugOnly<bool> exists;
|
|
MOZ_ASSERT(NS_SUCCEEDED(file->Exists(&exists)));
|
|
MOZ_ASSERT(exists);
|
|
|
|
DebugOnly<bool> isFile;
|
|
MOZ_ASSERT(NS_SUCCEEDED(file->IsFile(&isFile)));
|
|
MOZ_ASSERT(isFile);
|
|
|
|
return file.forget();
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
FileManager::InitDirectory(nsIFile* aDirectory,
|
|
nsIFile* aDatabaseFile,
|
|
PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
uint32_t aTelemetryId)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aDirectory);
|
|
MOZ_ASSERT(aDatabaseFile);
|
|
|
|
bool exists;
|
|
nsresult rv = aDirectory->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!exists) {
|
|
return NS_OK;
|
|
}
|
|
|
|
bool isDirectory;
|
|
rv = aDirectory->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!isDirectory)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> journalDirectory;
|
|
rv = aDirectory->Clone(getter_AddRefs(journalDirectory));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = journalDirectory->Append(NS_LITERAL_STRING(JOURNAL_DIRECTORY_NAME));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = journalDirectory->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (exists) {
|
|
rv = journalDirectory->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!isDirectory)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsISimpleEnumerator> entries;
|
|
rv = journalDirectory->GetDirectoryEntries(getter_AddRefs(entries));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool hasElements;
|
|
rv = entries->HasMoreElements(&hasElements);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (hasElements) {
|
|
nsCOMPtr<mozIStorageConnection> connection;
|
|
rv = CreateStorageConnection(aDatabaseFile,
|
|
aDirectory,
|
|
NullString(),
|
|
aPersistenceType,
|
|
aGroup,
|
|
aOrigin,
|
|
aTelemetryId,
|
|
getter_AddRefs(connection));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mozStorageTransaction transaction(connection, false);
|
|
|
|
rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE VIRTUAL TABLE fs USING filesystem;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = connection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT name, (name IN (SELECT id FROM file)) "
|
|
"FROM fs "
|
|
"WHERE path = :path"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsString path;
|
|
rv = journalDirectory->GetPath(path);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("path"), path);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool hasResult;
|
|
while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
|
|
nsString name;
|
|
rv = stmt->GetString(0, name);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
int32_t flag = stmt->AsInt32(1);
|
|
|
|
if (!flag) {
|
|
nsCOMPtr<nsIFile> file;
|
|
rv = aDirectory->Clone(getter_AddRefs(file));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = file->Append(name);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_FAILED(file->Remove(false))) {
|
|
NS_WARNING("Failed to remove orphaned file!");
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> journalFile;
|
|
rv = journalDirectory->Clone(getter_AddRefs(journalFile));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = journalFile->Append(name);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_FAILED(journalFile->Remove(false))) {
|
|
NS_WARNING("Failed to remove journal file!");
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE fs;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = transaction.Commit();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
FileManager::GetUsage(nsIFile* aDirectory, uint64_t* aUsage)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aDirectory);
|
|
MOZ_ASSERT(aUsage);
|
|
|
|
bool exists;
|
|
nsresult rv = aDirectory->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!exists) {
|
|
*aUsage = 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsISimpleEnumerator> entries;
|
|
rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
uint64_t usage = 0;
|
|
|
|
bool hasMore;
|
|
while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) && hasMore) {
|
|
nsCOMPtr<nsISupports> entry;
|
|
rv = entries->GetNext(getter_AddRefs(entry));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
|
|
MOZ_ASSERT(file);
|
|
|
|
nsString leafName;
|
|
rv = file->GetLeafName(leafName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (leafName.EqualsLiteral(JOURNAL_DIRECTORY_NAME)) {
|
|
continue;
|
|
}
|
|
|
|
int64_t fileSize;
|
|
rv = file->GetFileSize(&fileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
UsageInfo::IncrementUsage(&usage, uint64_t(fileSize));
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
*aUsage = usage;
|
|
return NS_OK;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* FileImplStoredFile
|
|
******************************************************************************/
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED(BlobImplStoredFile,
|
|
BlobImplFile,
|
|
BlobImplStoredFile)
|
|
|
|
/*******************************************************************************
|
|
* QuotaClient
|
|
******************************************************************************/
|
|
|
|
QuotaClient* QuotaClient::sInstance = nullptr;
|
|
|
|
QuotaClient::QuotaClient()
|
|
: mShutdownRequested(false)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");
|
|
MOZ_ASSERT(!gTelemetryIdMutex);
|
|
|
|
// Always create this so that later access to gTelemetryIdHashtable can be
|
|
// properly synchronized.
|
|
gTelemetryIdMutex = new Mutex("IndexedDB gTelemetryIdMutex");
|
|
|
|
sInstance = this;
|
|
}
|
|
|
|
QuotaClient::~QuotaClient()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");
|
|
MOZ_ASSERT(gTelemetryIdMutex);
|
|
MOZ_ASSERT(!mMaintenanceThreadPool);
|
|
|
|
// No one else should be able to touch gTelemetryIdHashtable now that the
|
|
// QuotaClient has gone away.
|
|
gTelemetryIdHashtable = nullptr;
|
|
gTelemetryIdMutex = nullptr;
|
|
|
|
sInstance = nullptr;
|
|
}
|
|
|
|
nsThreadPool*
|
|
QuotaClient::GetOrCreateThreadPool()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mShutdownRequested);
|
|
|
|
if (!mMaintenanceThreadPool) {
|
|
RefPtr<nsThreadPool> threadPool = new nsThreadPool();
|
|
|
|
// PR_GetNumberOfProcessors() can return -1 on error, so make sure we
|
|
// don't set some huge number here. We add 2 in case some threads block on
|
|
// the disk I/O.
|
|
const uint32_t threadCount =
|
|
std::max(int32_t(PR_GetNumberOfProcessors()), int32_t(1)) +
|
|
2;
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(threadPool->SetThreadLimit(threadCount));
|
|
|
|
// Don't keep more than one idle thread.
|
|
MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadLimit(1));
|
|
|
|
// Don't keep idle threads alive very long.
|
|
MOZ_ALWAYS_SUCCEEDS(threadPool->SetIdleThreadTimeout(5 * PR_MSEC_PER_SEC));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(threadPool->SetName(NS_LITERAL_CSTRING("IndexedDB Mnt")));
|
|
|
|
mMaintenanceThreadPool = Move(threadPool);
|
|
}
|
|
|
|
return mMaintenanceThreadPool;
|
|
}
|
|
|
|
mozilla::dom::quota::Client::Type
|
|
QuotaClient::GetType()
|
|
{
|
|
return QuotaClient::IDB;
|
|
}
|
|
|
|
struct FileManagerInitInfo
|
|
{
|
|
nsCOMPtr<nsIFile> mDirectory;
|
|
nsCOMPtr<nsIFile> mDatabaseFile;
|
|
nsCOMPtr<nsIFile> mDatabaseWALFile;
|
|
};
|
|
|
|
nsresult
|
|
QuotaClient::InitOrigin(PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
UsageInfo* aUsageInfo)
|
|
{
|
|
AssertIsOnIOThread();
|
|
|
|
nsCOMPtr<nsIFile> directory;
|
|
nsresult rv =
|
|
GetDirectory(aPersistenceType, aOrigin, getter_AddRefs(directory));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// We need to see if there are any files in the directory already. If they
|
|
// are database files then we need to cleanup stored files (if it's needed)
|
|
// and also get the usage.
|
|
|
|
AutoTArray<nsString, 20> subdirsToProcess;
|
|
nsTArray<nsCOMPtr<nsIFile>> unknownFiles;
|
|
nsTHashtable<nsStringHashKey> validSubdirs(20);
|
|
AutoTArray<FileManagerInitInfo, 20> initInfos;
|
|
|
|
nsCOMPtr<nsISimpleEnumerator> entries;
|
|
rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
const NS_ConvertASCIItoUTF16 filesSuffix(
|
|
kFileManagerDirectoryNameSuffix,
|
|
LiteralStringLength(kFileManagerDirectoryNameSuffix));
|
|
|
|
const NS_ConvertASCIItoUTF16 journalSuffix(
|
|
kSQLiteJournalSuffix,
|
|
LiteralStringLength(kSQLiteJournalSuffix));
|
|
const NS_ConvertASCIItoUTF16 shmSuffix(kSQLiteSHMSuffix,
|
|
LiteralStringLength(kSQLiteSHMSuffix));
|
|
const NS_ConvertASCIItoUTF16 walSuffix(kSQLiteWALSuffix,
|
|
LiteralStringLength(kSQLiteWALSuffix));
|
|
|
|
bool hasMore;
|
|
while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
|
|
hasMore &&
|
|
(!aUsageInfo || !aUsageInfo->Canceled())) {
|
|
nsCOMPtr<nsISupports> entry;
|
|
rv = entries->GetNext(getter_AddRefs(entry));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
|
|
MOZ_ASSERT(file);
|
|
|
|
nsString leafName;
|
|
rv = file->GetLeafName(leafName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool isDirectory;
|
|
rv = file->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (isDirectory) {
|
|
if (!StringEndsWith(leafName, filesSuffix) ||
|
|
!validSubdirs.GetEntry(leafName)) {
|
|
subdirsToProcess.AppendElement(leafName);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Skip Desktop Service Store (.DS_Store) files. These files are only used
|
|
// on Mac OS X, but the profile can be shared across different operating
|
|
// systems, so we check it on all platforms.
|
|
if (leafName.EqualsLiteral(DSSTORE_FILE_NAME)) {
|
|
continue;
|
|
}
|
|
|
|
// Skip SQLite temporary files. These files take up space on disk but will
|
|
// be deleted as soon as the database is opened, so we don't count them
|
|
// towards quota.
|
|
if (StringEndsWith(leafName, journalSuffix) ||
|
|
StringEndsWith(leafName, shmSuffix)) {
|
|
continue;
|
|
}
|
|
|
|
// The SQLite WAL file does count towards quota, but it is handled below
|
|
// once we find the actual database file.
|
|
if (StringEndsWith(leafName, walSuffix)) {
|
|
continue;
|
|
}
|
|
|
|
nsDependentSubstring dbBaseFilename;
|
|
if (!GetDatabaseBaseFilename(leafName, dbBaseFilename)) {
|
|
unknownFiles.AppendElement(file);
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> fmDirectory;
|
|
rv = directory->Clone(getter_AddRefs(fmDirectory));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsString fmDirectoryBaseName = dbBaseFilename + filesSuffix;
|
|
|
|
rv = fmDirectory->Append(fmDirectoryBaseName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> walFile;
|
|
if (aUsageInfo) {
|
|
rv = directory->Clone(getter_AddRefs(walFile));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = walFile->Append(dbBaseFilename + walSuffix);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
FileManagerInitInfo* initInfo = initInfos.AppendElement();
|
|
initInfo->mDirectory.swap(fmDirectory);
|
|
initInfo->mDatabaseFile.swap(file);
|
|
initInfo->mDatabaseWALFile.swap(walFile);
|
|
|
|
validSubdirs.PutEntry(fmDirectoryBaseName);
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
for (uint32_t count = subdirsToProcess.Length(), i = 0; i < count; i++) {
|
|
const nsString& subdirName = subdirsToProcess[i];
|
|
|
|
// If the directory has the correct suffix then it must exist in
|
|
// validSubdirs.
|
|
if (StringEndsWith(subdirName, filesSuffix)) {
|
|
if (NS_WARN_IF(!validSubdirs.GetEntry(subdirName))) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// The directory didn't have the right suffix but we might need to rename
|
|
// it. Check to see if we have a database that references this directory.
|
|
nsString subdirNameWithSuffix = subdirName + filesSuffix;
|
|
if (!validSubdirs.GetEntry(subdirNameWithSuffix)) {
|
|
// Windows doesn't allow a directory to end with a dot ('.'), so we have
|
|
// to check that possibility here too.
|
|
// We do this on all platforms, because the origin directory may have
|
|
// been created on Windows and now accessed on different OS.
|
|
subdirNameWithSuffix = subdirName + NS_LITERAL_STRING(".") + filesSuffix;
|
|
if (NS_WARN_IF(!validSubdirs.GetEntry(subdirNameWithSuffix))) {
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
}
|
|
|
|
// We do have a database that uses this directory so we should rename it
|
|
// now. However, first check to make sure that we're not overwriting
|
|
// something else.
|
|
nsCOMPtr<nsIFile> subdir;
|
|
rv = directory->Clone(getter_AddRefs(subdir));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = subdir->Append(subdirNameWithSuffix);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool exists;
|
|
rv = subdir->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (exists) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
rv = directory->Clone(getter_AddRefs(subdir));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = subdir->Append(subdirName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
DebugOnly<bool> isDirectory;
|
|
MOZ_ASSERT(NS_SUCCEEDED(subdir->IsDirectory(&isDirectory)));
|
|
MOZ_ASSERT(isDirectory);
|
|
|
|
rv = subdir->RenameTo(nullptr, subdirNameWithSuffix);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
for (uint32_t count = initInfos.Length(), i = 0; i < count; i++) {
|
|
FileManagerInitInfo& initInfo = initInfos[i];
|
|
MOZ_ASSERT(initInfo.mDirectory);
|
|
MOZ_ASSERT(initInfo.mDatabaseFile);
|
|
MOZ_ASSERT_IF(aUsageInfo, initInfo.mDatabaseWALFile);
|
|
|
|
rv = FileManager::InitDirectory(initInfo.mDirectory,
|
|
initInfo.mDatabaseFile,
|
|
aPersistenceType,
|
|
aGroup,
|
|
aOrigin,
|
|
TelemetryIdForFile(initInfo.mDatabaseFile));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (aUsageInfo && !aUsageInfo->Canceled()) {
|
|
int64_t fileSize;
|
|
rv = initInfo.mDatabaseFile->GetFileSize(&fileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(fileSize >= 0);
|
|
|
|
aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
|
|
|
|
rv = initInfo.mDatabaseWALFile->GetFileSize(&fileSize);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
MOZ_ASSERT(fileSize >= 0);
|
|
aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
|
|
} else if (NS_WARN_IF(rv != NS_ERROR_FILE_NOT_FOUND &&
|
|
rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST)) {
|
|
return rv;
|
|
}
|
|
|
|
uint64_t usage;
|
|
rv = FileManager::GetUsage(initInfo.mDirectory, &usage);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
aUsageInfo->AppendToFileUsage(usage);
|
|
}
|
|
}
|
|
|
|
// We have to do this after file manager initialization.
|
|
if (!unknownFiles.IsEmpty()) {
|
|
#ifdef DEBUG
|
|
for (uint32_t count = unknownFiles.Length(), i = 0; i < count; i++) {
|
|
nsCOMPtr<nsIFile>& unknownFile = unknownFiles[i];
|
|
|
|
nsString leafName;
|
|
MOZ_ALWAYS_SUCCEEDS(unknownFile->GetLeafName(leafName));
|
|
|
|
MOZ_ASSERT(!StringEndsWith(leafName, journalSuffix));
|
|
MOZ_ASSERT(!StringEndsWith(leafName, shmSuffix));
|
|
MOZ_ASSERT(!StringEndsWith(leafName, walSuffix));
|
|
|
|
nsString path;
|
|
MOZ_ALWAYS_SUCCEEDS(unknownFile->GetPath(path));
|
|
MOZ_ASSERT(!path.IsEmpty());
|
|
|
|
nsPrintfCString warning(R"(Refusing to open databases for "%s" because )"
|
|
R"(an unexpected file exists in the storage )"
|
|
R"(area: "%s")",
|
|
PromiseFlatCString(aOrigin).get(),
|
|
NS_ConvertUTF16toUTF8(path).get());
|
|
NS_WARNING(warning.get());
|
|
}
|
|
#endif
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
QuotaClient::GetUsageForOrigin(PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
UsageInfo* aUsageInfo)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aUsageInfo);
|
|
|
|
nsCOMPtr<nsIFile> directory;
|
|
nsresult rv =
|
|
GetDirectory(aPersistenceType, aOrigin, getter_AddRefs(directory));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = GetUsageForDirectoryInternal(directory, aUsageInfo, true);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
QuotaClient::OnOriginClearCompleted(PersistenceType aPersistenceType,
|
|
const nsACString& aOrigin)
|
|
{
|
|
AssertIsOnIOThread();
|
|
|
|
if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
|
|
mgr->InvalidateFileManagers(aPersistenceType, aOrigin);
|
|
}
|
|
}
|
|
|
|
void
|
|
QuotaClient::ReleaseIOThreadObjects()
|
|
{
|
|
AssertIsOnIOThread();
|
|
|
|
if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
|
|
mgr->InvalidateAllFileManagers();
|
|
}
|
|
}
|
|
|
|
void
|
|
QuotaClient::AbortOperations(const nsACString& aOrigin)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (!gLiveDatabaseHashtable) {
|
|
return;
|
|
}
|
|
|
|
nsTArray<RefPtr<Database>> databases;
|
|
|
|
for (auto iter = gLiveDatabaseHashtable->ConstIter();
|
|
!iter.Done(); iter.Next()) {
|
|
for (Database* database : iter.Data()->mLiveDatabases) {
|
|
if (aOrigin.IsVoid() || database->Origin() == aOrigin) {
|
|
databases.AppendElement(database);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (Database* database : databases) {
|
|
database->Invalidate();
|
|
}
|
|
|
|
databases.Clear();
|
|
}
|
|
|
|
void
|
|
QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (!gLiveDatabaseHashtable) {
|
|
return;
|
|
}
|
|
|
|
nsTArray<RefPtr<Database>> databases;
|
|
|
|
for (auto iter = gLiveDatabaseHashtable->ConstIter();
|
|
!iter.Done(); iter.Next()) {
|
|
for (Database* database : iter.Data()->mLiveDatabases) {
|
|
if (database->IsOwnedByProcess(aContentParentId)) {
|
|
databases.AppendElement(database);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (Database* database : databases) {
|
|
database->Invalidate();
|
|
}
|
|
|
|
databases.Clear();
|
|
}
|
|
|
|
void
|
|
QuotaClient::StartIdleMaintenance()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mShutdownRequested);
|
|
|
|
mBackgroundThread = do_GetCurrentThread();
|
|
|
|
RefPtr<Maintenance> maintenance = new Maintenance(this);
|
|
|
|
mMaintenanceQueue.AppendElement(maintenance.forget());
|
|
ProcessMaintenanceQueue();
|
|
}
|
|
|
|
void
|
|
QuotaClient::StopIdleMaintenance()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mShutdownRequested);
|
|
|
|
if (mCurrentMaintenance) {
|
|
mCurrentMaintenance->Abort();
|
|
}
|
|
|
|
for (RefPtr<Maintenance>& maintenance : mMaintenanceQueue) {
|
|
maintenance->Abort();
|
|
}
|
|
}
|
|
|
|
void
|
|
QuotaClient::ShutdownWorkThreads()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mShutdownRequested);
|
|
|
|
mShutdownRequested = true;
|
|
|
|
if (mMaintenanceThreadPool) {
|
|
mMaintenanceThreadPool->Shutdown();
|
|
mMaintenanceThreadPool = nullptr;
|
|
}
|
|
|
|
RefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
|
|
if (connectionPool) {
|
|
connectionPool->Shutdown();
|
|
|
|
gConnectionPool = nullptr;
|
|
}
|
|
|
|
RefPtr<FileHandleThreadPool> fileHandleThreadPool =
|
|
gFileHandleThreadPool.get();
|
|
if (fileHandleThreadPool) {
|
|
fileHandleThreadPool->Shutdown();
|
|
|
|
gFileHandleThreadPool = nullptr;
|
|
}
|
|
}
|
|
|
|
void
|
|
QuotaClient::DidInitialize(QuotaManager* aQuotaManager)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
|
|
mgr->NoteLiveQuotaManager(aQuotaManager);
|
|
}
|
|
}
|
|
|
|
void
|
|
QuotaClient::WillShutdown()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
|
|
mgr->NoteShuttingDownQuotaManager();
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
QuotaClient::GetDirectory(PersistenceType aPersistenceType,
|
|
const nsACString& aOrigin, nsIFile** aDirectory)
|
|
{
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
NS_ASSERTION(quotaManager, "This should never fail!");
|
|
|
|
nsCOMPtr<nsIFile> directory;
|
|
nsresult rv = quotaManager->GetDirectoryForOrigin(aPersistenceType, aOrigin,
|
|
getter_AddRefs(directory));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(directory);
|
|
|
|
rv = directory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
directory.forget(aDirectory);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
QuotaClient::GetUsageForDirectoryInternal(nsIFile* aDirectory,
|
|
UsageInfo* aUsageInfo,
|
|
bool aDatabaseFiles)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aDirectory);
|
|
MOZ_ASSERT(aUsageInfo);
|
|
|
|
nsCOMPtr<nsISimpleEnumerator> entries;
|
|
nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!entries) {
|
|
return NS_OK;
|
|
}
|
|
|
|
const NS_ConvertASCIItoUTF16 journalSuffix(
|
|
kSQLiteJournalSuffix,
|
|
LiteralStringLength(kSQLiteJournalSuffix));
|
|
const NS_ConvertASCIItoUTF16 shmSuffix(kSQLiteSHMSuffix,
|
|
LiteralStringLength(kSQLiteSHMSuffix));
|
|
|
|
bool hasMore;
|
|
while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
|
|
hasMore &&
|
|
!aUsageInfo->Canceled()) {
|
|
nsCOMPtr<nsISupports> entry;
|
|
rv = entries->GetNext(getter_AddRefs(entry));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
|
|
MOZ_ASSERT(file);
|
|
|
|
nsString leafName;
|
|
rv = file->GetLeafName(leafName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Journal files and sqlite-shm files don't count towards usage.
|
|
if (StringEndsWith(leafName, journalSuffix) ||
|
|
StringEndsWith(leafName, shmSuffix)) {
|
|
continue;
|
|
}
|
|
|
|
bool isDirectory;
|
|
rv = file->IsDirectory(&isDirectory);
|
|
if (rv == NS_ERROR_FILE_NOT_FOUND ||
|
|
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
|
|
continue;
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (isDirectory) {
|
|
if (aDatabaseFiles) {
|
|
rv = GetUsageForDirectoryInternal(file, aUsageInfo, false);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
nsString leafName;
|
|
rv = file->GetLeafName(leafName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!leafName.EqualsLiteral(JOURNAL_DIRECTORY_NAME)) {
|
|
NS_WARNING("Unknown directory found!");
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
int64_t fileSize;
|
|
rv = file->GetFileSize(&fileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(fileSize >= 0);
|
|
|
|
if (aDatabaseFiles) {
|
|
aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
|
|
} else {
|
|
aUsageInfo->AppendToFileUsage(uint64_t(fileSize));
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
QuotaClient::ProcessMaintenanceQueue()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (mCurrentMaintenance || mMaintenanceQueue.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
mCurrentMaintenance = mMaintenanceQueue[0];
|
|
mMaintenanceQueue.RemoveElementAt(0);
|
|
|
|
mCurrentMaintenance->RunImmediately();
|
|
}
|
|
|
|
void
|
|
Maintenance::RegisterDatabaseMaintenance(
|
|
DatabaseMaintenance* aDatabaseMaintenance)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aDatabaseMaintenance);
|
|
MOZ_ASSERT(mState == State::BeginDatabaseMaintenance);
|
|
MOZ_ASSERT(!mDatabaseMaintenances.Get(aDatabaseMaintenance->DatabasePath()));
|
|
|
|
mDatabaseMaintenances.Put(aDatabaseMaintenance->DatabasePath(),
|
|
aDatabaseMaintenance);
|
|
}
|
|
|
|
void
|
|
Maintenance::UnregisterDatabaseMaintenance(
|
|
DatabaseMaintenance* aDatabaseMaintenance)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aDatabaseMaintenance);
|
|
MOZ_ASSERT(mState == State::WaitingForDatabaseMaintenancesToComplete);
|
|
MOZ_ASSERT(mDatabaseMaintenances.Get(aDatabaseMaintenance->DatabasePath()));
|
|
|
|
mDatabaseMaintenances.Remove(aDatabaseMaintenance->DatabasePath());
|
|
|
|
if (mDatabaseMaintenances.Count()) {
|
|
return;
|
|
}
|
|
|
|
mState = State::Finishing;
|
|
Finish();
|
|
}
|
|
|
|
nsresult
|
|
Maintenance::Start()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mState == State::Initial);
|
|
|
|
if (IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
// Make sure that the IndexedDatabaseManager is running so that we can check
|
|
// for low disk space mode.
|
|
|
|
if (IndexedDatabaseManager::Get()) {
|
|
OpenDirectory();
|
|
return NS_OK;
|
|
}
|
|
|
|
mState = State::CreateIndexedDatabaseManager;
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Maintenance::CreateIndexedDatabaseManager()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mState == State::CreateIndexedDatabaseManager);
|
|
|
|
if (IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
IndexedDatabaseManager* mgr = IndexedDatabaseManager::GetOrCreate();
|
|
if (NS_WARN_IF(!mgr)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mState = State::IndexedDatabaseManagerOpen;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
mQuotaClient->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Maintenance::OpenDirectory()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mState == State::Initial ||
|
|
mState == State::IndexedDatabaseManagerOpen);
|
|
MOZ_ASSERT(!mDirectoryLock);
|
|
MOZ_ASSERT(QuotaManager::Get());
|
|
|
|
if (IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
// Get a shared lock for <profile>/storage/*/*/idb
|
|
|
|
mState = State::DirectoryOpenPending;
|
|
QuotaManager::Get()->OpenDirectoryInternal(
|
|
Nullable<PersistenceType>(),
|
|
OriginScope::FromNull(),
|
|
Nullable<Client::Type>(Client::IDB),
|
|
/* aExclusive */ false,
|
|
this);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Maintenance::DirectoryOpen()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mState == State::DirectoryOpenPending);
|
|
MOZ_ASSERT(mDirectoryLock);
|
|
|
|
if (IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
mState = State::DirectoryWorkOpen;
|
|
|
|
nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Maintenance::DirectoryWork()
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(mState == State::DirectoryWorkOpen);
|
|
|
|
// The storage directory is structured like this:
|
|
//
|
|
// <profile>/storage/<persistence>/<origin>/idb/*.sqlite
|
|
//
|
|
// We have to find all database files that match any persistence type and any
|
|
// origin. We ignore anything out of the ordinary for now.
|
|
|
|
if (IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
if (NS_WARN_IF(NS_FAILED(quotaManager->EnsureStorageIsInitialized()))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> storageDir = GetFileForPath(quotaManager->GetStoragePath());
|
|
MOZ_ASSERT(storageDir);
|
|
|
|
bool exists;
|
|
MOZ_ALWAYS_SUCCEEDS(storageDir->Exists(&exists));
|
|
if (!exists) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
bool isDirectory;
|
|
MOZ_ALWAYS_SUCCEEDS(storageDir->IsDirectory(&isDirectory));
|
|
if (NS_WARN_IF(!isDirectory)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// There are currently only 3 persistence types, and we want to iterate them
|
|
// in this order:
|
|
static const PersistenceType kPersistenceTypes[] = {
|
|
PERSISTENCE_TYPE_PERSISTENT,
|
|
PERSISTENCE_TYPE_DEFAULT,
|
|
PERSISTENCE_TYPE_TEMPORARY
|
|
};
|
|
|
|
static_assert((sizeof(kPersistenceTypes) / sizeof(kPersistenceTypes[0])) ==
|
|
size_t(PERSISTENCE_TYPE_INVALID),
|
|
"Something changed with available persistence types!");
|
|
|
|
NS_NAMED_LITERAL_STRING(idbDirName, IDB_DIRECTORY_NAME);
|
|
NS_NAMED_LITERAL_STRING(sqliteExtension, ".sqlite");
|
|
|
|
for (const PersistenceType persistenceType : kPersistenceTypes) {
|
|
// Loop over "<persistence>" directories.
|
|
if (IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
nsAutoCString persistenceTypeString;
|
|
if (persistenceType == PERSISTENCE_TYPE_PERSISTENT) {
|
|
// XXX This shouldn't be a special case...
|
|
persistenceTypeString.AssignLiteral("permanent");
|
|
} else {
|
|
PersistenceTypeToText(persistenceType, persistenceTypeString);
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> persistenceDir;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
storageDir->Clone(getter_AddRefs(persistenceDir)));
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
persistenceDir->Append(NS_ConvertASCIItoUTF16(persistenceTypeString)));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(persistenceDir->Exists(&exists));
|
|
if (!exists) {
|
|
continue;
|
|
}
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(persistenceDir->IsDirectory(&isDirectory));
|
|
if (NS_WARN_IF(!isDirectory)) {
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsISimpleEnumerator> persistenceDirEntries;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
persistenceDir->GetDirectoryEntries(
|
|
getter_AddRefs(persistenceDirEntries)));
|
|
if (!persistenceDirEntries) {
|
|
continue;
|
|
}
|
|
|
|
while (true) {
|
|
// Loop over "<origin>/idb" directories.
|
|
if (IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
bool persistenceDirHasMoreEntries;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
persistenceDirEntries->HasMoreElements(&persistenceDirHasMoreEntries));
|
|
|
|
if (!persistenceDirHasMoreEntries) {
|
|
break;
|
|
}
|
|
|
|
nsCOMPtr<nsISupports> persistenceDirEntry;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
persistenceDirEntries->GetNext(getter_AddRefs(persistenceDirEntry)));
|
|
|
|
nsCOMPtr<nsIFile> originDir = do_QueryInterface(persistenceDirEntry);
|
|
MOZ_ASSERT(originDir);
|
|
|
|
MOZ_ASSERT(NS_SUCCEEDED(originDir->Exists(&exists)));
|
|
MOZ_ASSERT(exists);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(originDir->IsDirectory(&isDirectory));
|
|
if (!isDirectory) {
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> idbDir;
|
|
MOZ_ALWAYS_SUCCEEDS(originDir->Clone(getter_AddRefs(idbDir)));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(idbDir->Append(idbDirName));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(idbDir->Exists(&exists));
|
|
if (!exists) {
|
|
continue;
|
|
}
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(idbDir->IsDirectory(&isDirectory));
|
|
if (NS_WARN_IF(!isDirectory)) {
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsISimpleEnumerator> idbDirEntries;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
idbDir->GetDirectoryEntries(getter_AddRefs(idbDirEntries)));
|
|
if (!idbDirEntries) {
|
|
continue;
|
|
}
|
|
|
|
nsCString group;
|
|
nsCString origin;
|
|
nsTArray<nsString> databasePaths;
|
|
|
|
while (true) {
|
|
// Loop over files in the "idb" directory.
|
|
if (IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
bool idbDirHasMoreEntries;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
idbDirEntries->HasMoreElements(&idbDirHasMoreEntries));
|
|
|
|
if (!idbDirHasMoreEntries) {
|
|
break;
|
|
}
|
|
|
|
nsCOMPtr<nsISupports> idbDirEntry;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
idbDirEntries->GetNext(getter_AddRefs(idbDirEntry)));
|
|
|
|
nsCOMPtr<nsIFile> idbDirFile = do_QueryInterface(idbDirEntry);
|
|
MOZ_ASSERT(idbDirFile);
|
|
|
|
nsString idbFilePath;
|
|
MOZ_ALWAYS_SUCCEEDS(idbDirFile->GetPath(idbFilePath));
|
|
|
|
if (!StringEndsWith(idbFilePath, sqliteExtension)) {
|
|
continue;
|
|
}
|
|
|
|
MOZ_ASSERT(NS_SUCCEEDED(idbDirFile->Exists(&exists)));
|
|
MOZ_ASSERT(exists);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(idbDirFile->IsDirectory(&isDirectory));
|
|
if (isDirectory) {
|
|
continue;
|
|
}
|
|
|
|
// Found a database.
|
|
if (databasePaths.IsEmpty()) {
|
|
MOZ_ASSERT(group.IsEmpty());
|
|
MOZ_ASSERT(origin.IsEmpty());
|
|
|
|
int64_t dummyTimeStamp;
|
|
nsCString dummySuffix;
|
|
bool dummyIsApp;
|
|
if (NS_WARN_IF(NS_FAILED(
|
|
quotaManager->GetDirectoryMetadata2(originDir,
|
|
&dummyTimeStamp,
|
|
dummySuffix,
|
|
group,
|
|
origin,
|
|
&dummyIsApp)))) {
|
|
// Not much we can do here...
|
|
continue;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(!databasePaths.Contains(idbFilePath));
|
|
|
|
databasePaths.AppendElement(idbFilePath);
|
|
}
|
|
|
|
if (!databasePaths.IsEmpty()) {
|
|
mDirectoryInfos.AppendElement(DirectoryInfo(persistenceType,
|
|
group,
|
|
origin,
|
|
Move(databasePaths)));
|
|
}
|
|
}
|
|
}
|
|
|
|
mState = State::BeginDatabaseMaintenance;
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
mQuotaClient->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Maintenance::BeginDatabaseMaintenance()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mState == State::BeginDatabaseMaintenance);
|
|
|
|
class MOZ_STACK_CLASS Helper final
|
|
{
|
|
public:
|
|
static bool
|
|
IsSafeToRunMaintenance(const nsAString& aDatabasePath)
|
|
{
|
|
if (gFactoryOps) {
|
|
for (uint32_t index = gFactoryOps->Length(); index > 0; index--) {
|
|
RefPtr<FactoryOp>& existingOp = (*gFactoryOps)[index - 1];
|
|
|
|
MOZ_ASSERT(!existingOp->DatabaseFilePath().IsEmpty());
|
|
|
|
if (existingOp->DatabaseFilePath() == aDatabasePath) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (gLiveDatabaseHashtable) {
|
|
for (auto iter = gLiveDatabaseHashtable->ConstIter();
|
|
!iter.Done(); iter.Next()) {
|
|
for (Database* database : iter.Data()->mLiveDatabases) {
|
|
if (database->FilePath() == aDatabasePath) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
RefPtr<nsThreadPool> threadPool;
|
|
|
|
for (DirectoryInfo& directoryInfo : mDirectoryInfos) {
|
|
for (const nsString& databasePath : directoryInfo.mDatabasePaths) {
|
|
if (Helper::IsSafeToRunMaintenance(databasePath)) {
|
|
RefPtr<DatabaseMaintenance> databaseMaintenance =
|
|
new DatabaseMaintenance(this,
|
|
directoryInfo.mPersistenceType,
|
|
directoryInfo.mGroup,
|
|
directoryInfo.mOrigin,
|
|
databasePath);
|
|
|
|
if (!threadPool) {
|
|
threadPool = mQuotaClient->GetOrCreateThreadPool();
|
|
MOZ_ASSERT(threadPool);
|
|
}
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
threadPool->Dispatch(databaseMaintenance, NS_DISPATCH_NORMAL));
|
|
|
|
RegisterDatabaseMaintenance(databaseMaintenance);
|
|
}
|
|
}
|
|
}
|
|
|
|
mDirectoryInfos.Clear();
|
|
|
|
if (mDatabaseMaintenances.Count()) {
|
|
mState = State::WaitingForDatabaseMaintenancesToComplete;
|
|
} else {
|
|
mState = State::Finishing;
|
|
Finish();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
Maintenance::Finish()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mState == State::Finishing);
|
|
|
|
mDirectoryLock = nullptr;
|
|
|
|
mQuotaClient->NoteFinishedMaintenance(this);
|
|
|
|
mState = State::Complete;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED0(Maintenance, Runnable)
|
|
|
|
NS_IMETHODIMP
|
|
Maintenance::Run()
|
|
{
|
|
MOZ_ASSERT(mState != State::Complete);
|
|
|
|
nsresult rv;
|
|
|
|
switch (mState) {
|
|
case State::Initial:
|
|
rv = Start();
|
|
break;
|
|
|
|
case State::CreateIndexedDatabaseManager:
|
|
rv = CreateIndexedDatabaseManager();
|
|
break;
|
|
|
|
case State::IndexedDatabaseManagerOpen:
|
|
rv = OpenDirectory();
|
|
break;
|
|
|
|
case State::DirectoryWorkOpen:
|
|
rv = DirectoryWork();
|
|
break;
|
|
|
|
case State::BeginDatabaseMaintenance:
|
|
rv = BeginDatabaseMaintenance();
|
|
break;
|
|
|
|
case State::Finishing:
|
|
Finish();
|
|
return NS_OK;
|
|
|
|
default:
|
|
MOZ_CRASH("Bad state!");
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::Finishing) {
|
|
// Must set mState before dispatching otherwise we will race with the owning
|
|
// thread.
|
|
mState = State::Finishing;
|
|
|
|
if (IsOnBackgroundThread()) {
|
|
Finish();
|
|
} else {
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
mQuotaClient->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
Maintenance::DirectoryLockAcquired(DirectoryLock* aLock)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mState == State::DirectoryOpenPending);
|
|
MOZ_ASSERT(!mDirectoryLock);
|
|
|
|
mDirectoryLock = aLock;
|
|
|
|
nsresult rv = DirectoryOpen();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
mState = State::Finishing;
|
|
Finish();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
Maintenance::DirectoryLockFailed()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mState == State::DirectoryOpenPending);
|
|
MOZ_ASSERT(!mDirectoryLock);
|
|
|
|
mState = State::Finishing;
|
|
Finish();
|
|
}
|
|
|
|
void
|
|
DatabaseMaintenance::PerformMaintenanceOnDatabase()
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(mMaintenance);
|
|
MOZ_ASSERT(mMaintenance->StartTime());
|
|
MOZ_ASSERT(!mDatabasePath.IsEmpty());
|
|
MOZ_ASSERT(!mGroup.IsEmpty());
|
|
MOZ_ASSERT(!mOrigin.IsEmpty());
|
|
|
|
class MOZ_STACK_CLASS AutoClose final
|
|
{
|
|
nsCOMPtr<mozIStorageConnection> mConnection;
|
|
|
|
public:
|
|
explicit AutoClose(mozIStorageConnection* aConnection)
|
|
: mConnection(aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
}
|
|
|
|
~AutoClose()
|
|
{
|
|
MOZ_ASSERT(mConnection);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(mConnection->Close());
|
|
}
|
|
};
|
|
|
|
nsCOMPtr<nsIFile> databaseFile = GetFileForPath(mDatabasePath);
|
|
MOZ_ASSERT(databaseFile);
|
|
|
|
nsCOMPtr<mozIStorageConnection> connection;
|
|
nsresult rv = GetStorageConnection(databaseFile,
|
|
mPersistenceType,
|
|
mGroup,
|
|
mOrigin,
|
|
TelemetryIdForFile(databaseFile),
|
|
getter_AddRefs(connection));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
AutoClose autoClose(connection);
|
|
|
|
if (mMaintenance->IsAborted()) {
|
|
return;
|
|
}
|
|
|
|
AutoProgressHandler progressHandler(mMaintenance);
|
|
if (NS_WARN_IF(NS_FAILED(progressHandler.Register(connection)))) {
|
|
return;
|
|
}
|
|
|
|
bool databaseIsOk;
|
|
rv = CheckIntegrity(connection, &databaseIsOk);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!databaseIsOk)) {
|
|
// XXX Handle this somehow! Probably need to clear all storage for the
|
|
// origin. Needs followup.
|
|
MOZ_ASSERT(false, "Database corruption detected!");
|
|
return;
|
|
}
|
|
|
|
if (mMaintenance->IsAborted()) {
|
|
return;
|
|
}
|
|
|
|
MaintenanceAction maintenanceAction;
|
|
rv = DetermineMaintenanceAction(connection, databaseFile, &maintenanceAction);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
if (mMaintenance->IsAborted()) {
|
|
return;
|
|
}
|
|
|
|
switch (maintenanceAction) {
|
|
case MaintenanceAction::Nothing:
|
|
break;
|
|
|
|
case MaintenanceAction::IncrementalVacuum:
|
|
IncrementalVacuum(connection);
|
|
break;
|
|
|
|
case MaintenanceAction::FullVacuum:
|
|
FullVacuum(connection, databaseFile);
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Unknown MaintenanceAction!");
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
DatabaseMaintenance::CheckIntegrity(mozIStorageConnection* aConnection,
|
|
bool* aOk)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aConnection);
|
|
MOZ_ASSERT(aOk);
|
|
|
|
nsresult rv;
|
|
|
|
// First do a full integrity_check. Scope statements tightly here because
|
|
// later operations require zero live statements.
|
|
{
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"PRAGMA integrity_check(1);"
|
|
), 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);
|
|
|
|
nsString result;
|
|
rv = stmt->GetString(0, result);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!result.EqualsLiteral("ok"))) {
|
|
*aOk = false;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// Now enable and check for foreign key constraints.
|
|
{
|
|
int32_t foreignKeysWereEnabled;
|
|
{
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"PRAGMA foreign_keys;"
|
|
), 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);
|
|
|
|
rv = stmt->GetInt32(0, &foreignKeysWereEnabled);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (!foreignKeysWereEnabled) {
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"PRAGMA foreign_keys = ON;"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
bool foreignKeyError;
|
|
{
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"PRAGMA foreign_key_check;"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->ExecuteStep(&foreignKeyError);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (!foreignKeysWereEnabled) {
|
|
nsAutoCString stmtSQL;
|
|
stmtSQL.AssignLiteral("PRAGMA foreign_keys = ");
|
|
stmtSQL.AppendLiteral("OFF");
|
|
stmtSQL.Append(';');
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(stmtSQL);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (foreignKeyError) {
|
|
*aOk = false;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
*aOk = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseMaintenance::DetermineMaintenanceAction(
|
|
mozIStorageConnection* aConnection,
|
|
nsIFile* aDatabaseFile,
|
|
MaintenanceAction* aMaintenanceAction)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aConnection);
|
|
MOZ_ASSERT(aDatabaseFile);
|
|
MOZ_ASSERT(aMaintenanceAction);
|
|
|
|
int32_t schemaVersion;
|
|
nsresult rv = aConnection->GetSchemaVersion(&schemaVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Don't do anything if the schema version is less than 18; before that
|
|
// version no databases had |auto_vacuum == INCREMENTAL| set and we didn't
|
|
// track the values needed for the heuristics below.
|
|
if (schemaVersion < MakeSchemaVersion(18, 0)) {
|
|
*aMaintenanceAction = MaintenanceAction::Nothing;
|
|
return NS_OK;
|
|
}
|
|
|
|
bool lowDiskSpace = IndexedDatabaseManager::InLowDiskSpaceMode();
|
|
|
|
if (QuotaManager::IsRunningXPCShellTests()) {
|
|
// If we're running XPCShell then we want to test both the low disk space
|
|
// and normal disk space code paths so pick semi-randomly based on the
|
|
// current time.
|
|
lowDiskSpace = ((PR_Now() / PR_USEC_PER_MSEC) % 2) == 0;
|
|
}
|
|
|
|
// If we're low on disk space then the best we can hope for is that an
|
|
// incremental vacuum might free some space. That is a journaled operation so
|
|
// it may not be possible even then.
|
|
if (lowDiskSpace) {
|
|
*aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
|
|
return NS_OK;
|
|
}
|
|
|
|
// This method shouldn't make any permanent changes to the database, so make
|
|
// sure everything gets rolled back when we leave.
|
|
mozStorageTransaction transaction(aConnection,
|
|
/* aCommitOnComplete */ false);
|
|
|
|
// Check to see when we last vacuumed this database.
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT last_vacuum_time, last_vacuum_size "
|
|
"FROM database;"
|
|
), 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);
|
|
|
|
PRTime lastVacuumTime;
|
|
rv = stmt->GetInt64(0, &lastVacuumTime);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
int64_t lastVacuumSize;
|
|
rv = stmt->GetInt64(1, &lastVacuumSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
NS_ASSERTION(lastVacuumSize > 0, "Thy last vacuum size shall be greater than zero, less than zero shall thy last vacuum size not be. Zero is right out.");
|
|
|
|
PRTime startTime = mMaintenance->StartTime();
|
|
|
|
// This shouldn't really be possible...
|
|
if (NS_WARN_IF(startTime <= lastVacuumTime)) {
|
|
*aMaintenanceAction = MaintenanceAction::Nothing;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (startTime - lastVacuumTime < kMinVacuumAge) {
|
|
*aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
|
|
return NS_OK;
|
|
}
|
|
|
|
// It has been more than a week since the database was vacuumed, so gather
|
|
// statistics on its usage to see if vacuuming is worthwhile.
|
|
|
|
// Create a temporary copy of the dbstat table to speed up the queries that
|
|
// come later.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE VIRTUAL TABLE __stats__ USING dbstat;"
|
|
"CREATE TEMP TABLE __temp_stats__ AS SELECT * FROM __stats__;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Calculate the percentage of the database pages that are not in contiguous
|
|
// order.
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT SUM(__ts1__.pageno != __ts2__.pageno + 1) * 100.0 / COUNT(*) "
|
|
"FROM __temp_stats__ AS __ts1__, __temp_stats__ AS __ts2__ "
|
|
"WHERE __ts1__.name = __ts2__.name "
|
|
"AND __ts1__.rowid = __ts2__.rowid + 1;"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->ExecuteStep(&hasResult);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(hasResult);
|
|
|
|
int32_t percentUnordered;
|
|
rv = stmt->GetInt32(0, &percentUnordered);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(percentUnordered >= 0);
|
|
MOZ_ASSERT(percentUnordered <= 100);
|
|
|
|
if (percentUnordered >= kPercentUnorderedThreshold) {
|
|
*aMaintenanceAction = MaintenanceAction::FullVacuum;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Don't try a full vacuum if the file hasn't grown by 10%.
|
|
int64_t currentFileSize;
|
|
rv = aDatabaseFile->GetFileSize(¤tFileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (currentFileSize <= lastVacuumSize ||
|
|
(((currentFileSize - lastVacuumSize) * 100 / currentFileSize) <
|
|
kPercentFileSizeGrowthThreshold)) {
|
|
*aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
|
|
return NS_OK;
|
|
}
|
|
|
|
// See if there are any free pages that we can reclaim.
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"PRAGMA freelist_count;"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->ExecuteStep(&hasResult);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(hasResult);
|
|
|
|
int32_t freelistCount;
|
|
rv = stmt->GetInt32(0, &freelistCount);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(freelistCount >= 0);
|
|
|
|
// If we have too many free pages then we should try an incremental vacuum. If
|
|
// that causes too much fragmentation then we'll try a full vacuum later.
|
|
if (freelistCount > kMaxFreelistThreshold) {
|
|
*aMaintenanceAction = MaintenanceAction::IncrementalVacuum;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Calculate the percentage of unused bytes on pages in the database.
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT SUM(unused) * 100.0 / SUM(pgsize) "
|
|
"FROM __temp_stats__;"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->ExecuteStep(&hasResult);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(hasResult);
|
|
|
|
int32_t percentUnused;
|
|
rv = stmt->GetInt32(0, &percentUnused);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(percentUnused >= 0);
|
|
MOZ_ASSERT(percentUnused <= 100);
|
|
|
|
*aMaintenanceAction = percentUnused >= kPercentUnusedThreshold ?
|
|
MaintenanceAction::FullVacuum :
|
|
MaintenanceAction::IncrementalVacuum;
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DatabaseMaintenance::IncrementalVacuum(mozIStorageConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"PRAGMA incremental_vacuum;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
DatabaseMaintenance::FullVacuum(mozIStorageConnection* aConnection,
|
|
nsIFile* aDatabaseFile)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aConnection);
|
|
MOZ_ASSERT(aDatabaseFile);
|
|
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"VACUUM;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
PRTime vacuumTime = PR_Now();
|
|
MOZ_ASSERT(vacuumTime > 0);
|
|
|
|
int64_t fileSize;
|
|
rv = aDatabaseFile->GetFileSize(&fileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(fileSize > 0);
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"UPDATE database "
|
|
"SET last_vacuum_time = :time"
|
|
", last_vacuum_size = :size;"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("time"), vacuumTime);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("size"), fileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
DatabaseMaintenance::RunOnOwningThread()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (mCompleteCallback) {
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mCompleteCallback.forget()));
|
|
}
|
|
|
|
mMaintenance->UnregisterDatabaseMaintenance(this);
|
|
}
|
|
|
|
void
|
|
DatabaseMaintenance::RunOnConnectionThread()
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
PerformMaintenanceOnDatabase();
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
mMaintenance->BackgroundThread()->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DatabaseMaintenance::Run()
|
|
{
|
|
if (IsOnBackgroundThread()) {
|
|
RunOnOwningThread();
|
|
} else {
|
|
RunOnConnectionThread();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DatabaseMaintenance::
|
|
AutoProgressHandler::Register(mozIStorageConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
// We want to quickly bail out of any operation if the user becomes active, so
|
|
// use a small granularity here since database performance isn't critical.
|
|
static const int32_t kProgressGranularity = 50;
|
|
|
|
nsCOMPtr<mozIStorageProgressHandler> oldHandler;
|
|
nsresult rv = aConnection->SetProgressHandler(kProgressGranularity,
|
|
this,
|
|
getter_AddRefs(oldHandler));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(!oldHandler);
|
|
mConnection = aConnection;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DatabaseMaintenance::
|
|
AutoProgressHandler::Unregister()
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(mConnection);
|
|
|
|
nsCOMPtr<mozIStorageProgressHandler> oldHandler;
|
|
nsresult rv = mConnection->RemoveProgressHandler(getter_AddRefs(oldHandler));
|
|
Unused << NS_WARN_IF(NS_FAILED(rv));
|
|
|
|
MOZ_ASSERT_IF(NS_SUCCEEDED(rv), oldHandler == this);
|
|
}
|
|
|
|
NS_IMETHODIMP_(MozExternalRefCountType)
|
|
DatabaseMaintenance::
|
|
AutoProgressHandler::AddRef()
|
|
{
|
|
NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
|
|
|
|
#ifdef DEBUG
|
|
mDEBUGRefCnt++;
|
|
#endif
|
|
return 2;
|
|
}
|
|
|
|
NS_IMETHODIMP_(MozExternalRefCountType)
|
|
DatabaseMaintenance::
|
|
AutoProgressHandler::Release()
|
|
{
|
|
NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
|
|
|
|
#ifdef DEBUG
|
|
mDEBUGRefCnt--;
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
NS_IMPL_QUERY_INTERFACE(DatabaseMaintenance::AutoProgressHandler,
|
|
mozIStorageProgressHandler)
|
|
|
|
NS_IMETHODIMP
|
|
DatabaseMaintenance::
|
|
AutoProgressHandler::OnProgress(mozIStorageConnection* aConnection,
|
|
bool* _retval)
|
|
{
|
|
NS_ASSERT_OWNINGTHREAD(DatabaseMaintenance::AutoProgressHandler);
|
|
MOZ_ASSERT(aConnection);
|
|
MOZ_ASSERT(mConnection == aConnection);
|
|
MOZ_ASSERT(_retval);
|
|
|
|
*_retval = mMaintenance->IsAborted();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Local class implementations
|
|
******************************************************************************/
|
|
|
|
NS_IMPL_ISUPPORTS(CompressDataBlobsFunction, mozIStorageFunction)
|
|
NS_IMPL_ISUPPORTS(EncodeKeysFunction, mozIStorageFunction)
|
|
|
|
#if !defined(MOZ_B2G)
|
|
|
|
nsresult
|
|
UpgradeFileIdsFunction::Init(nsIFile* aFMDirectory,
|
|
mozIStorageConnection* aConnection)
|
|
{
|
|
// This file manager doesn't need real origin info, etc. The only purpose is
|
|
// to store file ids without adding more complexity or code duplication.
|
|
RefPtr<FileManager> fileManager =
|
|
new FileManager(PERSISTENCE_TYPE_INVALID,
|
|
EmptyCString(),
|
|
EmptyCString(),
|
|
false,
|
|
EmptyString(),
|
|
false);
|
|
|
|
nsresult rv = fileManager->Init(aFMDirectory, aConnection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsAutoPtr<NormalJSContext> context(NormalJSContext::Create());
|
|
if (NS_WARN_IF(!context)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mFileManager.swap(fileManager);
|
|
mContext = context;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(UpgradeFileIdsFunction, mozIStorageFunction)
|
|
|
|
NS_IMETHODIMP
|
|
UpgradeFileIdsFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
|
|
nsIVariant** aResult)
|
|
{
|
|
MOZ_ASSERT(aArguments);
|
|
MOZ_ASSERT(aResult);
|
|
MOZ_ASSERT(mFileManager);
|
|
MOZ_ASSERT(mContext);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeFileIdsFunction::OnFunctionCall",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
uint32_t argc;
|
|
nsresult rv = aArguments->GetNumEntries(&argc);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (argc != 2) {
|
|
NS_WARNING("Don't call me with the wrong number of arguments!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
StructuredCloneReadInfo cloneInfo;
|
|
DatabaseOperationBase::GetStructuredCloneReadInfoFromValueArray(aArguments,
|
|
1,
|
|
0,
|
|
mFileManager,
|
|
&cloneInfo);
|
|
|
|
JSContext* cx = mContext->Context();
|
|
JSAutoRequest ar(cx);
|
|
JSAutoCompartment ac(cx, mContext->Global());
|
|
|
|
JS::Rooted<JS::Value> clone(cx);
|
|
if (NS_WARN_IF(!IDBObjectStore::DeserializeUpgradeValue(cx, cloneInfo,
|
|
&clone))) {
|
|
return NS_ERROR_DOM_DATA_CLONE_ERR;
|
|
}
|
|
|
|
nsAutoString fileIds;
|
|
|
|
for (uint32_t count = cloneInfo.mFiles.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
StructuredCloneFile& file = cloneInfo.mFiles[index];
|
|
MOZ_ASSERT(file.mFileInfo);
|
|
|
|
const int64_t id = file.mFileInfo->Id();
|
|
|
|
if (index) {
|
|
fileIds.Append(' ');
|
|
}
|
|
fileIds.AppendInt(file.mType == StructuredCloneFile::eBlob ? id : -id);
|
|
}
|
|
|
|
nsCOMPtr<nsIVariant> result = new mozilla::storage::TextVariant(fileIds);
|
|
|
|
result.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
#endif // MOZ_B2G
|
|
|
|
// static
|
|
void
|
|
DatabaseOperationBase::GetBindingClauseForKeyRange(
|
|
const SerializedKeyRange& aKeyRange,
|
|
const nsACString& aKeyColumnName,
|
|
nsAutoCString& aBindingClause)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(!aKeyColumnName.IsEmpty());
|
|
|
|
NS_NAMED_LITERAL_CSTRING(andStr, " AND ");
|
|
NS_NAMED_LITERAL_CSTRING(spacecolon, " :");
|
|
NS_NAMED_LITERAL_CSTRING(lowerKey, "lower_key");
|
|
|
|
if (aKeyRange.isOnly()) {
|
|
// Both keys equal.
|
|
aBindingClause = andStr + aKeyColumnName + NS_LITERAL_CSTRING(" =") +
|
|
spacecolon + lowerKey;
|
|
return;
|
|
}
|
|
|
|
aBindingClause.Truncate();
|
|
|
|
if (!aKeyRange.lower().IsUnset()) {
|
|
// Lower key is set.
|
|
aBindingClause.Append(andStr + aKeyColumnName);
|
|
aBindingClause.AppendLiteral(" >");
|
|
if (!aKeyRange.lowerOpen()) {
|
|
aBindingClause.AppendLiteral("=");
|
|
}
|
|
aBindingClause.Append(spacecolon + lowerKey);
|
|
}
|
|
|
|
if (!aKeyRange.upper().IsUnset()) {
|
|
// Upper key is set.
|
|
aBindingClause.Append(andStr + aKeyColumnName);
|
|
aBindingClause.AppendLiteral(" <");
|
|
if (!aKeyRange.upperOpen()) {
|
|
aBindingClause.AppendLiteral("=");
|
|
}
|
|
aBindingClause.Append(spacecolon + NS_LITERAL_CSTRING("upper_key"));
|
|
}
|
|
|
|
MOZ_ASSERT(!aBindingClause.IsEmpty());
|
|
}
|
|
|
|
// static
|
|
uint64_t
|
|
DatabaseOperationBase::ReinterpretDoubleAsUInt64(double aDouble)
|
|
{
|
|
// This is a duplicate of the js engine's byte munging in StructuredClone.cpp
|
|
return BitwiseCast<uint64_t>(aDouble);
|
|
}
|
|
|
|
// static
|
|
template <typename T>
|
|
nsresult
|
|
DatabaseOperationBase::GetStructuredCloneReadInfoFromSource(
|
|
T* aSource,
|
|
uint32_t aDataIndex,
|
|
uint32_t aFileIdsIndex,
|
|
FileManager* aFileManager,
|
|
StructuredCloneReadInfo* aInfo)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aSource);
|
|
MOZ_ASSERT(aFileManager);
|
|
MOZ_ASSERT(aInfo);
|
|
|
|
int32_t columnType;
|
|
nsresult rv = aSource->GetTypeOfIndex(aDataIndex, &columnType);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_BLOB ||
|
|
columnType == mozIStorageStatement::VALUE_TYPE_INTEGER);
|
|
|
|
bool isNull;
|
|
rv = aSource->GetIsNull(aFileIdsIndex, &isNull);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsString fileIds;
|
|
|
|
if (isNull) {
|
|
fileIds.SetIsVoid(true);
|
|
} else {
|
|
rv = aSource->GetString(aFileIdsIndex, fileIds);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (columnType == mozIStorageStatement::VALUE_TYPE_INTEGER) {
|
|
uint64_t intData;
|
|
rv = aSource->GetInt64(aDataIndex, reinterpret_cast<int64_t*>(&intData));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = GetStructuredCloneReadInfoFromExternalBlob(intData,
|
|
aFileManager,
|
|
fileIds,
|
|
aInfo);
|
|
} else {
|
|
const uint8_t* blobData;
|
|
uint32_t blobDataLength;
|
|
nsresult rv =
|
|
aSource->GetSharedBlob(aDataIndex, &blobDataLength, &blobData);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = GetStructuredCloneReadInfoFromBlob(blobData,
|
|
blobDataLength,
|
|
aFileManager,
|
|
fileIds,
|
|
aInfo);
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
DatabaseOperationBase::GetStructuredCloneReadInfoFromBlob(
|
|
const uint8_t* aBlobData,
|
|
uint32_t aBlobDataLength,
|
|
FileManager* aFileManager,
|
|
const nsAString& aFileIds,
|
|
StructuredCloneReadInfo* aInfo)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aFileManager);
|
|
MOZ_ASSERT(aInfo);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseOperationBase::GetStructuredCloneReadInfoFromBlob",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const char* compressed = reinterpret_cast<const char*>(aBlobData);
|
|
size_t compressedLength = size_t(aBlobDataLength);
|
|
|
|
size_t uncompressedLength;
|
|
if (NS_WARN_IF(!snappy::GetUncompressedLength(compressed, compressedLength,
|
|
&uncompressedLength))) {
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
AutoTArray<uint8_t, 512> uncompressed;
|
|
if (NS_WARN_IF(!uncompressed.SetLength(uncompressedLength, fallible))) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
char* uncompressedBuffer = reinterpret_cast<char*>(uncompressed.Elements());
|
|
|
|
if (NS_WARN_IF(!snappy::RawUncompress(compressed, compressedLength,
|
|
uncompressedBuffer))) {
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
if (!aInfo->mData.WriteBytes(uncompressedBuffer, uncompressed.Length())) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
if (!aFileIds.IsVoid()) {
|
|
nsresult rv = DeserializeStructuredCloneFiles(aFileManager,
|
|
aFileIds,
|
|
aInfo->mFiles,
|
|
&aInfo->mHasPreprocessInfo);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
DatabaseOperationBase::GetStructuredCloneReadInfoFromExternalBlob(
|
|
uint64_t aIntData,
|
|
FileManager* aFileManager,
|
|
const nsAString& aFileIds,
|
|
StructuredCloneReadInfo* aInfo)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aFileManager);
|
|
MOZ_ASSERT(aInfo);
|
|
|
|
PROFILER_LABEL(
|
|
"IndexedDB",
|
|
"DatabaseOperationBase::GetStructuredCloneReadInfoFromExternalBlob",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv;
|
|
|
|
if (!aFileIds.IsVoid()) {
|
|
rv = DeserializeStructuredCloneFiles(aFileManager,
|
|
aFileIds,
|
|
aInfo->mFiles,
|
|
&aInfo->mHasPreprocessInfo);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Higher and lower 32 bits described
|
|
// in ObjectStoreAddOrPutRequestOp::DoDatabaseWork.
|
|
uint32_t index = uint32_t(aIntData & 0xFFFFFFFF);
|
|
|
|
if (index >= aInfo->mFiles.Length()) {
|
|
MOZ_ASSERT(false, "Bad index value!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
StructuredCloneFile& file = aInfo->mFiles[index];
|
|
MOZ_ASSERT(file.mFileInfo);
|
|
MOZ_ASSERT(file.mType == StructuredCloneFile::eStructuredClone);
|
|
|
|
nsCOMPtr<nsIFile> nativeFile = GetFileForFileInfo(file.mFileInfo);
|
|
if (NS_WARN_IF(!nativeFile)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIInputStream> fileInputStream;
|
|
rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), nativeFile);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
RefPtr<SnappyUncompressInputStream> snappyInputStream =
|
|
new SnappyUncompressInputStream(fileInputStream);
|
|
|
|
do {
|
|
char buffer[kFileCopyBufferSize];
|
|
|
|
uint32_t numRead;
|
|
rv = snappyInputStream->Read(buffer, sizeof(buffer), &numRead);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
break;
|
|
}
|
|
|
|
if (!numRead) {
|
|
break;
|
|
}
|
|
|
|
if (NS_WARN_IF(!aInfo->mData.WriteBytes(buffer, numRead))) {
|
|
rv = NS_ERROR_OUT_OF_MEMORY;
|
|
break;
|
|
}
|
|
} while (true);
|
|
|
|
return rv;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
DatabaseOperationBase::BindKeyRangeToStatement(
|
|
const SerializedKeyRange& aKeyRange,
|
|
mozIStorageStatement* aStatement)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aStatement);
|
|
|
|
nsresult rv = NS_OK;
|
|
|
|
if (!aKeyRange.lower().IsUnset()) {
|
|
rv = aKeyRange.lower().BindToStatement(aStatement, NS_LITERAL_CSTRING("lower_key"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (aKeyRange.isOnly()) {
|
|
return rv;
|
|
}
|
|
|
|
if (!aKeyRange.upper().IsUnset()) {
|
|
rv = aKeyRange.upper().BindToStatement(aStatement, NS_LITERAL_CSTRING("upper_key"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
DatabaseOperationBase::BindKeyRangeToStatement(
|
|
const SerializedKeyRange& aKeyRange,
|
|
mozIStorageStatement* aStatement,
|
|
const nsCString& aLocale)
|
|
{
|
|
#ifndef ENABLE_INTL_API
|
|
return BindKeyRangeToStatement(aKeyRange, aStatement);
|
|
#else
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aStatement);
|
|
MOZ_ASSERT(!aLocale.IsEmpty());
|
|
|
|
nsresult rv = NS_OK;
|
|
|
|
if (!aKeyRange.lower().IsUnset()) {
|
|
Key lower;
|
|
rv = aKeyRange.lower().ToLocaleBasedKey(lower, aLocale);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = lower.BindToStatement(aStatement, NS_LITERAL_CSTRING("lower_key"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (aKeyRange.isOnly()) {
|
|
return rv;
|
|
}
|
|
|
|
if (!aKeyRange.upper().IsUnset()) {
|
|
Key upper;
|
|
rv = aKeyRange.upper().ToLocaleBasedKey(upper, aLocale);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = upper.BindToStatement(aStatement, NS_LITERAL_CSTRING("upper_key"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
#endif
|
|
}
|
|
|
|
// static
|
|
void
|
|
DatabaseOperationBase::AppendConditionClause(const nsACString& aColumnName,
|
|
const nsACString& aArgName,
|
|
bool aLessThan,
|
|
bool aEquals,
|
|
nsAutoCString& aResult)
|
|
{
|
|
aResult += NS_LITERAL_CSTRING(" AND ") + aColumnName +
|
|
NS_LITERAL_CSTRING(" ");
|
|
|
|
if (aLessThan) {
|
|
aResult.Append('<');
|
|
}
|
|
else {
|
|
aResult.Append('>');
|
|
}
|
|
|
|
if (aEquals) {
|
|
aResult.Append('=');
|
|
}
|
|
|
|
aResult += NS_LITERAL_CSTRING(" :") + aArgName;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
DatabaseOperationBase::GetUniqueIndexTableForObjectStore(
|
|
TransactionBase* aTransaction,
|
|
int64_t aObjectStoreId,
|
|
Maybe<UniqueIndexTable>& aMaybeUniqueIndexTable)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aTransaction);
|
|
MOZ_ASSERT(aObjectStoreId);
|
|
MOZ_ASSERT(aMaybeUniqueIndexTable.isNothing());
|
|
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
aTransaction->GetMetadataForObjectStoreId(aObjectStoreId);
|
|
MOZ_ASSERT(objectStoreMetadata);
|
|
|
|
if (!objectStoreMetadata->mIndexes.Count()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
const uint32_t indexCount = objectStoreMetadata->mIndexes.Count();
|
|
MOZ_ASSERT(indexCount > 0);
|
|
|
|
aMaybeUniqueIndexTable.emplace();
|
|
UniqueIndexTable* uniqueIndexTable = aMaybeUniqueIndexTable.ptr();
|
|
MOZ_ASSERT(uniqueIndexTable);
|
|
|
|
for (auto iter = objectStoreMetadata->mIndexes.Iter(); !iter.Done(); iter.Next()) {
|
|
FullIndexMetadata* value = iter.UserData();
|
|
MOZ_ASSERT(!uniqueIndexTable->Get(value->mCommonMetadata.id()));
|
|
|
|
if (NS_WARN_IF(!uniqueIndexTable->Put(value->mCommonMetadata.id(),
|
|
value->mCommonMetadata.unique(),
|
|
fallible))) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(aMaybeUniqueIndexTable.ref().Count() != indexCount)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
aMaybeUniqueIndexTable.reset();
|
|
NS_WARNING("out of memory");
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
aMaybeUniqueIndexTable.ref().MarkImmutable();
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
DatabaseOperationBase::IndexDataValuesFromUpdateInfos(
|
|
const nsTArray<IndexUpdateInfo>& aUpdateInfos,
|
|
const UniqueIndexTable& aUniqueIndexTable,
|
|
nsTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
MOZ_ASSERT(aIndexValues.IsEmpty());
|
|
MOZ_ASSERT_IF(!aUpdateInfos.IsEmpty(), aUniqueIndexTable.Count());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseOperationBase::IndexDataValuesFromUpdateInfos",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const uint32_t count = aUpdateInfos.Length();
|
|
|
|
if (!count) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (NS_WARN_IF(!aIndexValues.SetCapacity(count, fallible))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
for (uint32_t idxIndex = 0; idxIndex < count; idxIndex++) {
|
|
const IndexUpdateInfo& updateInfo = aUpdateInfos[idxIndex];
|
|
const int64_t& indexId = updateInfo.indexId();
|
|
const Key& key = updateInfo.value();
|
|
const Key& sortKey = updateInfo.localizedValue();
|
|
|
|
bool unique = false;
|
|
MOZ_ALWAYS_TRUE(aUniqueIndexTable.Get(indexId, &unique));
|
|
|
|
IndexDataValue idv(indexId, unique, key, sortKey);
|
|
|
|
MOZ_ALWAYS_TRUE(
|
|
aIndexValues.InsertElementSorted(idv, fallible));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
DatabaseOperationBase::InsertIndexTableRows(
|
|
DatabaseConnection* aConnection,
|
|
const int64_t aObjectStoreId,
|
|
const Key& aObjectStoreKey,
|
|
const FallibleTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(!aObjectStoreKey.IsUnset());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseOperationBase::InsertIndexTableRows",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const uint32_t count = aIndexValues.Length();
|
|
if (!count) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
|
|
NS_NAMED_LITERAL_CSTRING(objectDataKeyString, "object_data_key");
|
|
NS_NAMED_LITERAL_CSTRING(indexIdString, "index_id");
|
|
NS_NAMED_LITERAL_CSTRING(valueString, "value");
|
|
NS_NAMED_LITERAL_CSTRING(valueLocaleString, "value_locale");
|
|
|
|
DatabaseConnection::CachedStatement insertUniqueStmt;
|
|
DatabaseConnection::CachedStatement insertStmt;
|
|
|
|
nsresult rv;
|
|
|
|
for (uint32_t index = 0; index < count; index++) {
|
|
const IndexDataValue& info = aIndexValues[index];
|
|
|
|
DatabaseConnection::CachedStatement& stmt =
|
|
info.mUnique ? insertUniqueStmt : insertStmt;
|
|
|
|
if (stmt) {
|
|
stmt.Reset();
|
|
} else if (info.mUnique) {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"INSERT INTO unique_index_data "
|
|
"(index_id, value, object_store_id, object_data_key, value_locale) "
|
|
"VALUES (:index_id, :value, :object_store_id, :object_data_key, :value_locale);"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"INSERT OR IGNORE INTO index_data "
|
|
"(index_id, value, object_data_key, object_store_id, value_locale) "
|
|
"VALUES (:index_id, :value, :object_data_key, :object_store_id, :value_locale);"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(indexIdString, info.mIndexId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = info.mKey.BindToStatement(stmt, valueString);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = info.mSortKey.BindToStatement(stmt, valueLocaleString);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(objectStoreIdString, aObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aObjectStoreKey.BindToStatement(stmt, objectDataKeyString);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (rv == NS_ERROR_STORAGE_CONSTRAINT && info.mUnique) {
|
|
// If we're inserting multiple entries for the same unique index, then
|
|
// we might have failed to insert due to colliding with another entry for
|
|
// the same index in which case we should ignore it.
|
|
for (int32_t index2 = int32_t(index) - 1;
|
|
index2 >= 0 && aIndexValues[index2].mIndexId == info.mIndexId;
|
|
--index2) {
|
|
if (info.mKey == aIndexValues[index2].mKey) {
|
|
// We found a key with the same value for the same index. So we
|
|
// must have had a collision with a value we just inserted.
|
|
rv = NS_OK;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
DatabaseOperationBase::DeleteIndexDataTableRows(
|
|
DatabaseConnection* aConnection,
|
|
const Key& aObjectStoreKey,
|
|
const FallibleTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(!aObjectStoreKey.IsUnset());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseOperationBase::DeleteIndexDataTableRows",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const uint32_t count = aIndexValues.Length();
|
|
if (!count) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_NAMED_LITERAL_CSTRING(indexIdString, "index_id");
|
|
NS_NAMED_LITERAL_CSTRING(valueString, "value");
|
|
NS_NAMED_LITERAL_CSTRING(objectDataKeyString, "object_data_key");
|
|
|
|
DatabaseConnection::CachedStatement deleteUniqueStmt;
|
|
DatabaseConnection::CachedStatement deleteStmt;
|
|
|
|
nsresult rv;
|
|
|
|
for (uint32_t index = 0; index < count; index++) {
|
|
const IndexDataValue& indexValue = aIndexValues[index];
|
|
|
|
DatabaseConnection::CachedStatement& stmt =
|
|
indexValue.mUnique ? deleteUniqueStmt : deleteStmt;
|
|
|
|
if (stmt) {
|
|
stmt.Reset();
|
|
} else if (indexValue.mUnique) {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM unique_index_data "
|
|
"WHERE index_id = :index_id "
|
|
"AND value = :value;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM index_data "
|
|
"WHERE index_id = :index_id "
|
|
"AND value = :value "
|
|
"AND object_data_key = :object_data_key;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(indexIdString, indexValue.mIndexId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = indexValue.mKey.BindToStatement(stmt, valueString);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!indexValue.mUnique) {
|
|
rv = aObjectStoreKey.BindToStatement(stmt, objectDataKeyString);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
DatabaseOperationBase::DeleteObjectStoreDataTableRowsWithIndexes(
|
|
DatabaseConnection* aConnection,
|
|
const int64_t aObjectStoreId,
|
|
const OptionalKeyRange& aKeyRange)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aObjectStoreId);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
bool hasIndexes = false;
|
|
MOZ_ASSERT(NS_SUCCEEDED(
|
|
ObjectStoreHasIndexes(aConnection, aObjectStoreId, &hasIndexes)));
|
|
MOZ_ASSERT(hasIndexes,
|
|
"Don't use this slow method if there are no indexes!");
|
|
}
|
|
#endif
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaseOperationBase::"
|
|
"DeleteObjectStoreDataTableRowsWithIndexes",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const bool singleRowOnly =
|
|
aKeyRange.type() == OptionalKeyRange::TSerializedKeyRange &&
|
|
aKeyRange.get_SerializedKeyRange().isOnly();
|
|
|
|
NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
|
|
NS_NAMED_LITERAL_CSTRING(keyString, "key");
|
|
|
|
nsresult rv;
|
|
Key objectStoreKey;
|
|
DatabaseConnection::CachedStatement selectStmt;
|
|
|
|
if (singleRowOnly) {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"SELECT index_data_values "
|
|
"FROM object_data "
|
|
"WHERE object_store_id = :object_store_id "
|
|
"AND key = :key;"),
|
|
&selectStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
objectStoreKey = aKeyRange.get_SerializedKeyRange().lower();
|
|
|
|
rv = objectStoreKey.BindToStatement(selectStmt, keyString);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
nsAutoCString keyRangeClause;
|
|
if (aKeyRange.type() == OptionalKeyRange::TSerializedKeyRange) {
|
|
GetBindingClauseForKeyRange(aKeyRange.get_SerializedKeyRange(),
|
|
keyString,
|
|
keyRangeClause);
|
|
}
|
|
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"SELECT index_data_values, key "
|
|
"FROM object_data "
|
|
"WHERE object_store_id = :") +
|
|
objectStoreIdString +
|
|
keyRangeClause +
|
|
NS_LITERAL_CSTRING(";"),
|
|
&selectStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (aKeyRange.type() == OptionalKeyRange::TSerializedKeyRange) {
|
|
rv = BindKeyRangeToStatement(aKeyRange, selectStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
|
|
rv = selectStmt->BindInt64ByName(objectStoreIdString, aObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
DatabaseConnection::CachedStatement deleteStmt;
|
|
AutoTArray<IndexDataValue, 32> indexValues;
|
|
|
|
DebugOnly<uint32_t> resultCountDEBUG = 0;
|
|
|
|
bool hasResult;
|
|
while (NS_SUCCEEDED(rv = selectStmt->ExecuteStep(&hasResult)) && hasResult) {
|
|
if (!singleRowOnly) {
|
|
rv = objectStoreKey.SetFromStatement(selectStmt, 1);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
indexValues.ClearAndRetainStorage();
|
|
}
|
|
|
|
rv = ReadCompressedIndexDataValues(selectStmt, 0, indexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = DeleteIndexDataTableRows(aConnection, objectStoreKey, indexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (deleteStmt) {
|
|
MOZ_ALWAYS_SUCCEEDS(deleteStmt->Reset());
|
|
} else {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM object_data "
|
|
"WHERE object_store_id = :object_store_id "
|
|
"AND key = :key;"),
|
|
&deleteStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = deleteStmt->BindInt64ByName(objectStoreIdString, aObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = objectStoreKey.BindToStatement(deleteStmt, keyString);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = deleteStmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
resultCountDEBUG++;
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT_IF(singleRowOnly, resultCountDEBUG <= 1);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
DatabaseOperationBase::UpdateIndexValues(
|
|
DatabaseConnection* aConnection,
|
|
const int64_t aObjectStoreId,
|
|
const Key& aObjectStoreKey,
|
|
const FallibleTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(!aObjectStoreKey.IsUnset());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DatabaunseOperationBase::UpdateIndexValues",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
UniqueFreePtr<uint8_t> indexDataValues;
|
|
uint32_t indexDataValuesLength;
|
|
nsresult rv = MakeCompressedIndexDataValues(aIndexValues,
|
|
indexDataValues,
|
|
&indexDataValuesLength);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(!indexDataValuesLength == !(indexDataValues.get()));
|
|
|
|
DatabaseConnection::CachedStatement updateStmt;
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"UPDATE object_data "
|
|
"SET index_data_values = :index_data_values "
|
|
"WHERE object_store_id = :object_store_id "
|
|
"AND key = :key;"),
|
|
&updateStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
NS_NAMED_LITERAL_CSTRING(indexDataValuesString, "index_data_values");
|
|
|
|
if (indexDataValues) {
|
|
rv = updateStmt->BindAdoptedBlobByName(indexDataValuesString,
|
|
indexDataValues.release(),
|
|
indexDataValuesLength);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
rv = updateStmt->BindNullByName(indexDataValuesString);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
|
|
aObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aObjectStoreKey.BindToStatement(updateStmt, NS_LITERAL_CSTRING("key"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = updateStmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
DatabaseOperationBase::ObjectStoreHasIndexes(DatabaseConnection* aConnection,
|
|
const int64_t aObjectStoreId,
|
|
bool* aHasIndexes)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aObjectStoreId);
|
|
MOZ_ASSERT(aHasIndexes);
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
|
|
nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"SELECT id "
|
|
"FROM object_store_index "
|
|
"WHERE object_store_id = :object_store_id "
|
|
"LIMIT 1;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
|
|
aObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool hasResult;
|
|
rv = stmt->ExecuteStep(&hasResult);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
*aHasIndexes = hasResult;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED(DatabaseOperationBase,
|
|
Runnable,
|
|
mozIStorageProgressHandler)
|
|
|
|
NS_IMETHODIMP
|
|
DatabaseOperationBase::OnProgress(mozIStorageConnection* aConnection,
|
|
bool* _retval)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aConnection);
|
|
MOZ_ASSERT(_retval);
|
|
|
|
// This is intentionally racy.
|
|
*_retval = !OperationMayProceed();
|
|
return NS_OK;
|
|
}
|
|
|
|
DatabaseOperationBase::
|
|
AutoSetProgressHandler::AutoSetProgressHandler()
|
|
: mConnection(nullptr)
|
|
#ifdef DEBUG
|
|
, mDEBUGDatabaseOp(nullptr)
|
|
#endif
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
}
|
|
|
|
DatabaseOperationBase::
|
|
AutoSetProgressHandler::~AutoSetProgressHandler()
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
if (mConnection) {
|
|
nsCOMPtr<mozIStorageProgressHandler> oldHandler;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
mConnection->RemoveProgressHandler(getter_AddRefs(oldHandler)));
|
|
MOZ_ASSERT(oldHandler == mDEBUGDatabaseOp);
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
DatabaseOperationBase::
|
|
AutoSetProgressHandler::Register(mozIStorageConnection* aConnection,
|
|
DatabaseOperationBase* aDatabaseOp)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aConnection);
|
|
MOZ_ASSERT(aDatabaseOp);
|
|
MOZ_ASSERT(!mConnection);
|
|
|
|
nsCOMPtr<mozIStorageProgressHandler> oldProgressHandler;
|
|
|
|
nsresult rv =
|
|
aConnection->SetProgressHandler(kStorageProgressGranularity,
|
|
aDatabaseOp,
|
|
getter_AddRefs(oldProgressHandler));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(!oldProgressHandler);
|
|
|
|
mConnection = aConnection;
|
|
#ifdef DEBUG
|
|
mDEBUGDatabaseOp = aDatabaseOp;
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
MutableFile::MutableFile(nsIFile* aFile,
|
|
Database* aDatabase,
|
|
FileInfo* aFileInfo)
|
|
: BackgroundMutableFileParentBase(FILE_HANDLE_STORAGE_IDB,
|
|
aDatabase->Id(),
|
|
IntString(aFileInfo->Id()),
|
|
aFile)
|
|
, mDatabase(aDatabase)
|
|
, mFileInfo(aFileInfo)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aDatabase);
|
|
MOZ_ASSERT(aFileInfo);
|
|
}
|
|
|
|
MutableFile::~MutableFile()
|
|
{
|
|
mDatabase->UnregisterMutableFile(this);
|
|
}
|
|
|
|
already_AddRefed<MutableFile>
|
|
MutableFile::Create(nsIFile* aFile,
|
|
Database* aDatabase,
|
|
FileInfo* aFileInfo)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
RefPtr<MutableFile> newMutableFile =
|
|
new MutableFile(aFile, aDatabase, aFileInfo);
|
|
|
|
if (!aDatabase->RegisterMutableFile(newMutableFile)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return newMutableFile.forget();
|
|
}
|
|
|
|
void
|
|
MutableFile::NoteActiveState()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
mDatabase->NoteActiveMutableFile();
|
|
}
|
|
|
|
void
|
|
MutableFile::NoteInactiveState()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
mDatabase->NoteInactiveMutableFile();
|
|
}
|
|
|
|
PBackgroundParent*
|
|
MutableFile::GetBackgroundParent() const
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
return GetDatabase()->GetBackgroundParent();
|
|
}
|
|
|
|
already_AddRefed<nsISupports>
|
|
MutableFile::CreateStream(bool aReadOnly)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
PersistenceType persistenceType = mDatabase->Type();
|
|
const nsACString& group = mDatabase->Group();
|
|
const nsACString& origin = mDatabase->Origin();
|
|
|
|
nsCOMPtr<nsISupports> result;
|
|
|
|
if (aReadOnly) {
|
|
RefPtr<FileInputStream> stream =
|
|
FileInputStream::Create(persistenceType, group, origin, mFile, -1, -1,
|
|
nsIFileInputStream::DEFER_OPEN);
|
|
result = NS_ISUPPORTS_CAST(nsIFileInputStream*, stream);
|
|
}
|
|
else {
|
|
RefPtr<FileStream> stream =
|
|
FileStream::Create(persistenceType, group, origin, mFile, -1, -1,
|
|
nsIFileStream::DEFER_OPEN);
|
|
result = NS_ISUPPORTS_CAST(nsIFileStream*, stream);
|
|
}
|
|
if (NS_WARN_IF(!result)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return result.forget();
|
|
}
|
|
|
|
already_AddRefed<BlobImpl>
|
|
MutableFile::CreateBlobImpl()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
RefPtr<BlobImpl> blobImpl =
|
|
new BlobImplStoredFile(mFile, mFileInfo, /* aSnapshot */ true);
|
|
return blobImpl.forget();
|
|
}
|
|
|
|
PBackgroundFileHandleParent*
|
|
MutableFile::AllocPBackgroundFileHandleParent(const FileMode& aMode)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
// Once a database is closed it must not try to open new file handles.
|
|
if (NS_WARN_IF(mDatabase->IsClosed())) {
|
|
if (!mDatabase->IsInvalidated()) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
if (!gFileHandleThreadPool) {
|
|
RefPtr<FileHandleThreadPool> fileHandleThreadPool =
|
|
FileHandleThreadPool::Create();
|
|
if (NS_WARN_IF(!fileHandleThreadPool)) {
|
|
return nullptr;
|
|
}
|
|
|
|
gFileHandleThreadPool = fileHandleThreadPool;
|
|
}
|
|
|
|
return BackgroundMutableFileParentBase::AllocPBackgroundFileHandleParent(
|
|
aMode);
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
MutableFile::RecvPBackgroundFileHandleConstructor(
|
|
PBackgroundFileHandleParent* aActor,
|
|
const FileMode& aMode)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mDatabase->IsClosed());
|
|
|
|
if (NS_WARN_IF(mDatabase->IsInvalidated())) {
|
|
// This is an expected race. We don't want the child to die here, just don't
|
|
// actually do any work.
|
|
return IPC_OK();
|
|
}
|
|
|
|
return BackgroundMutableFileParentBase::RecvPBackgroundFileHandleConstructor(
|
|
aActor, aMode);
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
MutableFile::RecvGetFileId(int64_t* aFileId)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mFileInfo);
|
|
|
|
if (NS_WARN_IF(!IndexedDatabaseManager::InTestingMode())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
*aFileId = mFileInfo->Id();
|
|
return IPC_OK();
|
|
}
|
|
|
|
FactoryOp::FactoryOp(Factory* aFactory,
|
|
already_AddRefed<ContentParent> aContentParent,
|
|
const CommonFactoryRequestParams& aCommonParams,
|
|
bool aDeleting)
|
|
: DatabaseOperationBase(aFactory->GetLoggingInfo()->Id(),
|
|
aFactory->GetLoggingInfo()->NextRequestSN())
|
|
, mFactory(aFactory)
|
|
, mContentParent(Move(aContentParent))
|
|
, mCommonParams(aCommonParams)
|
|
, mState(State::Initial)
|
|
, mIsApp(false)
|
|
, mEnforcingQuota(true)
|
|
, mDeleting(aDeleting)
|
|
, mBlockedDatabaseOpen(false)
|
|
, mChromeWriteAccessAllowed(false)
|
|
, mFileHandleDisabled(false)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aFactory);
|
|
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
|
|
}
|
|
|
|
nsresult
|
|
FactoryOp::Open()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mState == State::Initial);
|
|
|
|
// Swap this to the stack now to ensure that we release it on this thread.
|
|
RefPtr<ContentParent> contentParent;
|
|
mContentParent.swap(contentParent);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
!OperationMayProceed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
PermissionRequestBase::PermissionValue permission;
|
|
nsresult rv = CheckPermission(contentParent, &permission);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed ||
|
|
permission == PermissionRequestBase::kPermissionDenied ||
|
|
permission == PermissionRequestBase::kPermissionPrompt);
|
|
|
|
if (permission == PermissionRequestBase::kPermissionDenied) {
|
|
return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
|
|
}
|
|
|
|
{
|
|
// These services have to be started on the main thread currently.
|
|
|
|
IndexedDatabaseManager* mgr;
|
|
if (NS_WARN_IF(!(mgr = IndexedDatabaseManager::GetOrCreate()))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageService> ss;
|
|
if (NS_WARN_IF(!(ss = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID)))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
}
|
|
|
|
const DatabaseMetadata& metadata = mCommonParams.metadata();
|
|
|
|
QuotaManager::GetStorageId(metadata.persistenceType(),
|
|
mOrigin,
|
|
Client::IDB,
|
|
mDatabaseId);
|
|
|
|
mDatabaseId.Append('*');
|
|
mDatabaseId.Append(NS_ConvertUTF16toUTF8(metadata.name()));
|
|
|
|
if (permission == PermissionRequestBase::kPermissionPrompt) {
|
|
mState = State::PermissionChallenge;
|
|
MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed);
|
|
|
|
mState = State::FinishOpen;
|
|
MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
FactoryOp::ChallengePermission()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::PermissionChallenge);
|
|
|
|
const PrincipalInfo& principalInfo = mCommonParams.principalInfo();
|
|
MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
|
|
|
|
if (NS_WARN_IF(!SendPermissionChallenge(principalInfo))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
FactoryOp::RetryCheckPermission()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mState == State::PermissionRetry);
|
|
MOZ_ASSERT(mCommonParams.principalInfo().type() ==
|
|
PrincipalInfo::TContentPrincipalInfo);
|
|
|
|
// Swap this to the stack now to ensure that we release it on this thread.
|
|
RefPtr<ContentParent> contentParent;
|
|
mContentParent.swap(contentParent);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
!OperationMayProceed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
PermissionRequestBase::PermissionValue permission;
|
|
nsresult rv = CheckPermission(contentParent, &permission);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed ||
|
|
permission == PermissionRequestBase::kPermissionDenied ||
|
|
permission == PermissionRequestBase::kPermissionPrompt);
|
|
|
|
if (permission == PermissionRequestBase::kPermissionDenied ||
|
|
permission == PermissionRequestBase::kPermissionPrompt) {
|
|
return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
|
|
}
|
|
|
|
MOZ_ASSERT(permission == PermissionRequestBase::kPermissionAllowed);
|
|
|
|
mState = State::FinishOpen;
|
|
MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
FactoryOp::DirectoryOpen()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::DirectoryOpenPending);
|
|
MOZ_ASSERT(mDirectoryLock);
|
|
MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
|
|
|
|
// gFactoryOps could be null here if the child process crashed or something
|
|
// and that cleaned up the last Factory actor.
|
|
if (!gFactoryOps) {
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
// See if this FactoryOp needs to wait.
|
|
bool delayed = false;
|
|
for (uint32_t index = gFactoryOps->Length(); index > 0; index--) {
|
|
RefPtr<FactoryOp>& existingOp = (*gFactoryOps)[index - 1];
|
|
if (MustWaitFor(*existingOp)) {
|
|
// Only one op can be delayed.
|
|
MOZ_ASSERT(!existingOp->mDelayedOp);
|
|
existingOp->mDelayedOp = this;
|
|
delayed = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Adding this to the factory ops list will block any additional ops from
|
|
// proceeding until this one is done.
|
|
gFactoryOps->AppendElement(this);
|
|
|
|
if (!delayed) {
|
|
QuotaClient* quotaClient = QuotaClient::GetInstance();
|
|
MOZ_ASSERT(quotaClient);
|
|
|
|
if (RefPtr<Maintenance> currentMaintenance =
|
|
quotaClient->GetCurrentMaintenance()) {
|
|
if (RefPtr<DatabaseMaintenance> databaseMaintenance =
|
|
currentMaintenance->GetDatabaseMaintenance(mDatabaseFilePath)) {
|
|
databaseMaintenance->WaitForCompletion(this);
|
|
delayed = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
mBlockedDatabaseOpen = true;
|
|
|
|
// Balanced in FinishSendResults().
|
|
IncreaseBusyCount();
|
|
|
|
mState = State::DatabaseOpenPending;
|
|
if (!delayed) {
|
|
nsresult rv = DatabaseOpen();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
FactoryOp::SendToIOThread()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::DatabaseOpenPending);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
|
|
!OperationMayProceed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
// Must set this before dispatching otherwise we will race with the IO thread.
|
|
mState = State::DatabaseWorkOpen;
|
|
|
|
nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
FactoryOp::WaitForTransactions()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::BeginVersionChange ||
|
|
mState == State::WaitingForOtherDatabasesToClose);
|
|
MOZ_ASSERT(!mDatabaseId.IsEmpty());
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
mState = State::WaitingForTransactionsToComplete;
|
|
|
|
RefPtr<WaitForTransactionsHelper> helper =
|
|
new WaitForTransactionsHelper(mDatabaseId, this);
|
|
helper->WaitForTransactions();
|
|
}
|
|
|
|
void
|
|
FactoryOp::FinishSendResults()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::SendingResults);
|
|
MOZ_ASSERT(mFactory);
|
|
|
|
// Make sure to release the factory on this thread.
|
|
RefPtr<Factory> factory;
|
|
mFactory.swap(factory);
|
|
|
|
if (mBlockedDatabaseOpen) {
|
|
if (mDelayedOp) {
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToCurrentThread(mDelayedOp.forget()));
|
|
}
|
|
|
|
MOZ_ASSERT(gFactoryOps);
|
|
gFactoryOps->RemoveElement(this);
|
|
|
|
// Match the IncreaseBusyCount in DirectoryOpen().
|
|
DecreaseBusyCount();
|
|
}
|
|
|
|
mState = State::Completed;
|
|
}
|
|
|
|
nsresult
|
|
FactoryOp::CheckPermission(ContentParent* aContentParent,
|
|
PermissionRequestBase::PermissionValue* aPermission)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mState == State::Initial || mState == State::PermissionRetry);
|
|
|
|
const PrincipalInfo& principalInfo = mCommonParams.principalInfo();
|
|
if (principalInfo.type() != PrincipalInfo::TSystemPrincipalInfo &&
|
|
NS_WARN_IF(!Preferences::GetBool(kPrefIndexedDBEnabled, false))) {
|
|
if (aContentParent) {
|
|
// The DOM in the other process should have kept us from receiving any
|
|
// indexedDB messages so assume that the child is misbehaving.
|
|
aContentParent->KillHard("IndexedDB CheckPermission 1");
|
|
}
|
|
return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommonParams.privateBrowsingMode())) {
|
|
// XXX This is only temporary.
|
|
return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
|
|
}
|
|
|
|
mFileHandleDisabled = !Preferences::GetBool(kPrefFileHandleEnabled);
|
|
|
|
PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
|
|
|
|
MOZ_ASSERT(principalInfo.type() != PrincipalInfo::TNullPrincipalInfo);
|
|
|
|
if (principalInfo.type() == PrincipalInfo::TSystemPrincipalInfo) {
|
|
MOZ_ASSERT(mState == State::Initial);
|
|
MOZ_ASSERT(persistenceType == PERSISTENCE_TYPE_PERSISTENT);
|
|
|
|
if (aContentParent) {
|
|
// Check to make sure that the child process has access to the database it
|
|
// is accessing.
|
|
NS_NAMED_LITERAL_CSTRING(permissionStringBase,
|
|
PERMISSION_STRING_CHROME_BASE);
|
|
NS_ConvertUTF16toUTF8 databaseName(mCommonParams.metadata().name());
|
|
NS_NAMED_LITERAL_CSTRING(readSuffix, PERMISSION_STRING_CHROME_READ_SUFFIX);
|
|
NS_NAMED_LITERAL_CSTRING(writeSuffix, PERMISSION_STRING_CHROME_WRITE_SUFFIX);
|
|
|
|
const nsAutoCString permissionStringWrite =
|
|
permissionStringBase + databaseName + writeSuffix;
|
|
const nsAutoCString permissionStringRead =
|
|
permissionStringBase + databaseName + readSuffix;
|
|
|
|
bool canWrite =
|
|
CheckAtLeastOneAppHasPermission(aContentParent, permissionStringWrite);
|
|
|
|
bool canRead;
|
|
if (canWrite) {
|
|
MOZ_ASSERT(CheckAtLeastOneAppHasPermission(aContentParent,
|
|
permissionStringRead));
|
|
canRead = true;
|
|
} else {
|
|
canRead =
|
|
CheckAtLeastOneAppHasPermission(aContentParent, permissionStringRead);
|
|
}
|
|
|
|
// Deleting a database requires write permissions.
|
|
if (mDeleting && !canWrite) {
|
|
aContentParent->KillHard("IndexedDB CheckPermission 2");
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
// Opening or deleting requires read permissions.
|
|
if (!canRead) {
|
|
aContentParent->KillHard("IndexedDB CheckPermission 3");
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
mChromeWriteAccessAllowed = canWrite;
|
|
} else {
|
|
mChromeWriteAccessAllowed = true;
|
|
}
|
|
|
|
if (State::Initial == mState) {
|
|
QuotaManager::GetInfoForChrome(&mSuffix, &mGroup, &mOrigin, &mIsApp);
|
|
|
|
MOZ_ASSERT(!QuotaManager::IsFirstPromptRequired(persistenceType, mOrigin,
|
|
mIsApp));
|
|
|
|
mEnforcingQuota =
|
|
QuotaManager::IsQuotaEnforced(persistenceType, mOrigin, mIsApp);
|
|
}
|
|
|
|
*aPermission = PermissionRequestBase::kPermissionAllowed;
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(principalInfo.type() == PrincipalInfo::TContentPrincipalInfo);
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIPrincipal> principal =
|
|
PrincipalInfoToPrincipal(principalInfo, &rv);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCString suffix;
|
|
nsCString group;
|
|
nsCString origin;
|
|
bool isApp;
|
|
rv = QuotaManager::GetInfoFromPrincipal(principal,
|
|
&suffix,
|
|
&group,
|
|
&origin,
|
|
&isApp);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
#ifdef IDB_MOBILE
|
|
if (persistenceType == PERSISTENCE_TYPE_PERSISTENT &&
|
|
!QuotaManager::IsOriginWhitelistedForPersistentStorage(origin) &&
|
|
!isApp) {
|
|
return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
|
|
}
|
|
#endif
|
|
|
|
PermissionRequestBase::PermissionValue permission;
|
|
|
|
if (QuotaManager::IsFirstPromptRequired(persistenceType, origin, isApp)) {
|
|
rv = PermissionRequestBase::GetCurrentPermission(principal, &permission);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
permission = PermissionRequestBase::kPermissionAllowed;
|
|
}
|
|
|
|
if (permission != PermissionRequestBase::kPermissionDenied &&
|
|
State::Initial == mState) {
|
|
mSuffix = suffix;
|
|
mGroup = group;
|
|
mOrigin = origin;
|
|
mIsApp = isApp;
|
|
|
|
mEnforcingQuota =
|
|
QuotaManager::IsQuotaEnforced(persistenceType, mOrigin, mIsApp);
|
|
}
|
|
|
|
*aPermission = permission;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
FactoryOp::SendVersionChangeMessages(DatabaseActorInfo* aDatabaseActorInfo,
|
|
Database* aOpeningDatabase,
|
|
uint64_t aOldVersion,
|
|
const NullableVersion& aNewVersion)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aDatabaseActorInfo);
|
|
MOZ_ASSERT(mState == State::BeginVersionChange);
|
|
MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
const uint32_t expectedCount = mDeleting ? 0 : 1;
|
|
const uint32_t liveCount = aDatabaseActorInfo->mLiveDatabases.Length();
|
|
if (liveCount > expectedCount) {
|
|
FallibleTArray<MaybeBlockedDatabaseInfo> maybeBlockedDatabases;
|
|
for (uint32_t index = 0; index < liveCount; index++) {
|
|
Database* database = aDatabaseActorInfo->mLiveDatabases[index];
|
|
if ((!aOpeningDatabase || database != aOpeningDatabase) &&
|
|
!database->IsClosed() &&
|
|
NS_WARN_IF(!maybeBlockedDatabases.AppendElement(database, fallible))) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
if (!maybeBlockedDatabases.IsEmpty()) {
|
|
mMaybeBlockedDatabases.SwapElements(maybeBlockedDatabases);
|
|
}
|
|
}
|
|
|
|
if (!mMaybeBlockedDatabases.IsEmpty()) {
|
|
for (uint32_t count = mMaybeBlockedDatabases.Length(), index = 0;
|
|
index < count;
|
|
/* incremented conditionally */) {
|
|
if (mMaybeBlockedDatabases[index]->SendVersionChange(aOldVersion,
|
|
aNewVersion)) {
|
|
index++;
|
|
} else {
|
|
// We don't want to wait forever if we were not able to send the
|
|
// message.
|
|
mMaybeBlockedDatabases.RemoveElementAt(index);
|
|
count--;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
bool
|
|
FactoryOp::CheckAtLeastOneAppHasPermission(ContentParent* aContentParent,
|
|
const nsACString& aPermissionString)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aContentParent);
|
|
MOZ_ASSERT(!aPermissionString.IsEmpty());
|
|
|
|
return true;
|
|
}
|
|
|
|
nsresult
|
|
FactoryOp::FinishOpen()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::FinishOpen);
|
|
MOZ_ASSERT(!mContentParent);
|
|
|
|
if (QuotaManager::Get()) {
|
|
nsresult rv = OpenDirectory();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
mState = State::QuotaManagerPending;
|
|
QuotaManager::GetOrCreate(this);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
FactoryOp::QuotaManagerOpen()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::QuotaManagerPending);
|
|
|
|
if (NS_WARN_IF(!QuotaManager::Get())) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult rv = OpenDirectory();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
FactoryOp::OpenDirectory()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::FinishOpen ||
|
|
mState == State::QuotaManagerPending);
|
|
MOZ_ASSERT(!mOrigin.IsEmpty());
|
|
MOZ_ASSERT(!mDirectoryLock);
|
|
MOZ_ASSERT(!QuotaClient::IsShuttingDownOnBackgroundThread());
|
|
MOZ_ASSERT(QuotaManager::Get());
|
|
|
|
// Need to get database file path in advance.
|
|
const nsString& databaseName = mCommonParams.metadata().name();
|
|
PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
|
|
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
nsCOMPtr<nsIFile> dbFile;
|
|
nsresult rv = quotaManager->GetDirectoryForOrigin(persistenceType,
|
|
mOrigin,
|
|
getter_AddRefs(dbFile));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = dbFile->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsAutoString filename;
|
|
GetDatabaseFilename(databaseName, filename);
|
|
|
|
rv = dbFile->Append(filename + NS_LITERAL_STRING(".sqlite"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = dbFile->GetPath(mDatabaseFilePath);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mState = State::DirectoryOpenPending;
|
|
|
|
quotaManager->OpenDirectory(persistenceType,
|
|
mGroup,
|
|
mOrigin,
|
|
mIsApp,
|
|
Client::IDB,
|
|
/* aExclusive */ false,
|
|
this);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
FactoryOp::MustWaitFor(const FactoryOp& aExistingOp)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
// Things for the same persistence type, the same origin and the same
|
|
// database must wait.
|
|
return aExistingOp.mCommonParams.metadata().persistenceType() ==
|
|
mCommonParams.metadata().persistenceType() &&
|
|
aExistingOp.mOrigin == mOrigin &&
|
|
aExistingOp.mDatabaseId == mDatabaseId;
|
|
}
|
|
|
|
void
|
|
FactoryOp::NoteDatabaseBlocked(Database* aDatabase)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
|
|
MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
|
|
MOZ_ASSERT(mMaybeBlockedDatabases.Contains(aDatabase));
|
|
|
|
// Only send the blocked event if all databases have reported back. If the
|
|
// database was closed then it will have been removed from the array.
|
|
// Otherwise if it was blocked its |mBlocked| flag will be true.
|
|
bool sendBlockedEvent = true;
|
|
|
|
for (uint32_t count = mMaybeBlockedDatabases.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
MaybeBlockedDatabaseInfo& info = mMaybeBlockedDatabases[index];
|
|
if (info == aDatabase) {
|
|
// This database was blocked, mark accordingly.
|
|
info.mBlocked = true;
|
|
} else if (!info.mBlocked) {
|
|
// A database has not yet reported back yet, don't send the event yet.
|
|
sendBlockedEvent = false;
|
|
}
|
|
}
|
|
|
|
if (sendBlockedEvent) {
|
|
SendBlockedNotification();
|
|
}
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED0(FactoryOp, DatabaseOperationBase)
|
|
|
|
NS_IMETHODIMP
|
|
FactoryOp::Run()
|
|
{
|
|
nsresult rv;
|
|
|
|
switch (mState) {
|
|
case State::Initial:
|
|
rv = Open();
|
|
break;
|
|
|
|
case State::PermissionChallenge:
|
|
rv = ChallengePermission();
|
|
break;
|
|
|
|
case State::PermissionRetry:
|
|
rv = RetryCheckPermission();
|
|
break;
|
|
|
|
case State::FinishOpen:
|
|
rv = FinishOpen();
|
|
break;
|
|
|
|
case State::QuotaManagerPending:
|
|
rv = QuotaManagerOpen();
|
|
break;
|
|
|
|
case State::DatabaseOpenPending:
|
|
rv = DatabaseOpen();
|
|
break;
|
|
|
|
case State::DatabaseWorkOpen:
|
|
rv = DoDatabaseWork();
|
|
break;
|
|
|
|
case State::BeginVersionChange:
|
|
rv = BeginVersionChange();
|
|
break;
|
|
|
|
case State::WaitingForTransactionsToComplete:
|
|
rv = DispatchToWorkThread();
|
|
break;
|
|
|
|
case State::SendingResults:
|
|
SendResults();
|
|
return NS_OK;
|
|
|
|
default:
|
|
MOZ_CRASH("Bad state!");
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingResults) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = rv;
|
|
}
|
|
|
|
// Must set mState before dispatching otherwise we will race with the owning
|
|
// thread.
|
|
mState = State::SendingResults;
|
|
|
|
if (IsOnOwningThread()) {
|
|
SendResults();
|
|
} else {
|
|
MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
FactoryOp::DirectoryLockAcquired(DirectoryLock* aLock)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::DirectoryOpenPending);
|
|
MOZ_ASSERT(!mDirectoryLock);
|
|
|
|
mDirectoryLock = aLock;
|
|
|
|
nsresult rv = DirectoryOpen();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = rv;
|
|
}
|
|
|
|
mState = State::SendingResults;
|
|
SendResults();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
FactoryOp::DirectoryLockFailed()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::DirectoryOpenPending);
|
|
MOZ_ASSERT(!mDirectoryLock);
|
|
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
mState = State::SendingResults;
|
|
SendResults();
|
|
}
|
|
|
|
void
|
|
FactoryOp::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
NoteActorDestroyed();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
FactoryOp::RecvPermissionRetry()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
MOZ_ASSERT(mState == State::PermissionChallenge);
|
|
|
|
mContentParent = BackgroundParent::GetContentParent(Manager()->Manager());
|
|
|
|
mState = State::PermissionRetry;
|
|
MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(this));
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
OpenDatabaseOp::OpenDatabaseOp(Factory* aFactory,
|
|
already_AddRefed<ContentParent> aContentParent,
|
|
const CommonFactoryRequestParams& aParams)
|
|
: FactoryOp(aFactory, Move(aContentParent), aParams, /* aDeleting */ false)
|
|
, mMetadata(new FullDatabaseMetadata(aParams.metadata()))
|
|
, mRequestedVersion(aParams.metadata().version())
|
|
, mVersionChangeOp(nullptr)
|
|
, mTelemetryId(0)
|
|
{
|
|
if (mContentParent) {
|
|
// This is a little scary but it looks safe to call this off the main thread
|
|
// for now.
|
|
mOptionalContentParentId = Some(mContentParent->ChildID());
|
|
}
|
|
}
|
|
|
|
void
|
|
OpenDatabaseOp::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
FactoryOp::ActorDestroy(aWhy);
|
|
|
|
if (mVersionChangeOp) {
|
|
mVersionChangeOp->NoteActorDestroyed();
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
OpenDatabaseOp::DatabaseOpen()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::DatabaseOpenPending);
|
|
|
|
nsresult rv = SendToIOThread();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
OpenDatabaseOp::DoDatabaseWork()
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(mState == State::DatabaseWorkOpen);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"OpenDatabaseOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
!OperationMayProceed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
const nsString& databaseName = mCommonParams.metadata().name();
|
|
PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
|
|
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
nsCOMPtr<nsIFile> dbDirectory;
|
|
|
|
nsresult rv =
|
|
quotaManager->EnsureOriginIsInitialized(persistenceType,
|
|
mSuffix,
|
|
mGroup,
|
|
mOrigin,
|
|
mIsApp,
|
|
getter_AddRefs(dbDirectory));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = dbDirectory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool exists;
|
|
rv = dbDirectory->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!exists) {
|
|
rv = dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
#ifdef DEBUG
|
|
else {
|
|
bool isDirectory;
|
|
MOZ_ASSERT(NS_SUCCEEDED(dbDirectory->IsDirectory(&isDirectory)));
|
|
MOZ_ASSERT(isDirectory);
|
|
}
|
|
#endif
|
|
|
|
nsAutoString filename;
|
|
GetDatabaseFilename(databaseName, filename);
|
|
|
|
nsCOMPtr<nsIFile> dbFile;
|
|
rv = dbDirectory->Clone(getter_AddRefs(dbFile));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = dbFile->Append(filename + NS_LITERAL_STRING(".sqlite"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mTelemetryId = TelemetryIdForFile(dbFile);
|
|
|
|
#ifdef DEBUG
|
|
nsString databaseFilePath;
|
|
rv = dbFile->GetPath(databaseFilePath);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(databaseFilePath == mDatabaseFilePath);
|
|
#endif
|
|
|
|
nsCOMPtr<nsIFile> fmDirectory;
|
|
rv = dbDirectory->Clone(getter_AddRefs(fmDirectory));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
const NS_ConvertASCIItoUTF16 filesSuffix(kFileManagerDirectoryNameSuffix);
|
|
|
|
rv = fmDirectory->Append(filename + filesSuffix);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageConnection> connection;
|
|
rv = CreateStorageConnection(dbFile,
|
|
fmDirectory,
|
|
databaseName,
|
|
persistenceType,
|
|
mGroup,
|
|
mOrigin,
|
|
mTelemetryId,
|
|
getter_AddRefs(connection));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
AutoSetProgressHandler asph;
|
|
rv = asph.Register(connection, this);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = LoadDatabaseInformation(connection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(mMetadata->mNextObjectStoreId > mMetadata->mObjectStores.Count());
|
|
MOZ_ASSERT(mMetadata->mNextIndexId > 0);
|
|
|
|
// See if we need to do a versionchange transaction
|
|
|
|
// Optional version semantics.
|
|
if (!mRequestedVersion) {
|
|
// If the requested version was not specified and the database was created,
|
|
// treat it as if version 1 were requested.
|
|
if (mMetadata->mCommonMetadata.version() == 0) {
|
|
mRequestedVersion = 1;
|
|
} else {
|
|
// Otherwise, treat it as if the current version were requested.
|
|
mRequestedVersion = mMetadata->mCommonMetadata.version();
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(mMetadata->mCommonMetadata.version() > mRequestedVersion)) {
|
|
return NS_ERROR_DOM_INDEXEDDB_VERSION_ERR;
|
|
}
|
|
|
|
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
|
|
MOZ_ASSERT(mgr);
|
|
|
|
RefPtr<FileManager> fileManager =
|
|
mgr->GetFileManager(persistenceType, mOrigin, databaseName);
|
|
if (!fileManager) {
|
|
fileManager = new FileManager(persistenceType,
|
|
mGroup,
|
|
mOrigin,
|
|
mIsApp,
|
|
databaseName,
|
|
mEnforcingQuota);
|
|
|
|
rv = fileManager->Init(fmDirectory, connection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mgr->AddFileManager(fileManager);
|
|
}
|
|
|
|
mFileManager = fileManager.forget();
|
|
|
|
// Must set mState before dispatching otherwise we will race with the owning
|
|
// thread.
|
|
mState = (mMetadata->mCommonMetadata.version() == mRequestedVersion) ?
|
|
State::SendingResults :
|
|
State::BeginVersionChange;
|
|
|
|
rv = mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
OpenDatabaseOp::LoadDatabaseInformation(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
MOZ_ASSERT(mMetadata);
|
|
|
|
// Load version information.
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT name, origin, version "
|
|
"FROM database"
|
|
), 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;
|
|
}
|
|
|
|
if (NS_WARN_IF(!hasResult)) {
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
nsString databaseName;
|
|
rv = stmt->GetString(0, databaseName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommonParams.metadata().name() != databaseName)) {
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
nsCString origin;
|
|
rv = stmt->GetUTF8String(1, origin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (mOrigin != origin) {
|
|
NS_WARNING("Origins don't match!");
|
|
}
|
|
|
|
int64_t version;
|
|
rv = stmt->GetInt64(2, &version);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mMetadata->mCommonMetadata.version() = uint64_t(version);
|
|
|
|
ObjectStoreTable& objectStores = mMetadata->mObjectStores;
|
|
|
|
// Load object store names and ids.
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT id, auto_increment, name, key_path "
|
|
"FROM object_store"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
Maybe<nsTHashtable<nsUint64HashKey>> usedIds;
|
|
Maybe<nsTHashtable<nsStringHashKey>> usedNames;
|
|
|
|
int64_t lastObjectStoreId = 0;
|
|
|
|
while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
|
|
int64_t objectStoreId;
|
|
rv = stmt->GetInt64(0, &objectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!usedIds) {
|
|
usedIds.emplace();
|
|
}
|
|
|
|
if (NS_WARN_IF(objectStoreId <= 0) ||
|
|
NS_WARN_IF(usedIds.ref().Contains(objectStoreId))) {
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
if (NS_WARN_IF(!usedIds.ref().PutEntry(objectStoreId, fallible))) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
nsString name;
|
|
rv = stmt->GetString(2, name);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!usedNames) {
|
|
usedNames.emplace();
|
|
}
|
|
|
|
if (NS_WARN_IF(usedNames.ref().Contains(name))) {
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
if (NS_WARN_IF(!usedNames.ref().PutEntry(name, fallible))) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> metadata = new FullObjectStoreMetadata();
|
|
metadata->mCommonMetadata.id() = objectStoreId;
|
|
metadata->mCommonMetadata.name() = name;
|
|
|
|
int32_t columnType;
|
|
rv = stmt->GetTypeOfIndex(3, &columnType);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) {
|
|
metadata->mCommonMetadata.keyPath() = KeyPath(0);
|
|
} else {
|
|
MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_TEXT);
|
|
|
|
nsString keyPathSerialization;
|
|
rv = stmt->GetString(3, keyPathSerialization);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
metadata->mCommonMetadata.keyPath() =
|
|
KeyPath::DeserializeFromString(keyPathSerialization);
|
|
if (NS_WARN_IF(!metadata->mCommonMetadata.keyPath().IsValid())) {
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
}
|
|
|
|
int64_t nextAutoIncrementId;
|
|
rv = stmt->GetInt64(1, &nextAutoIncrementId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
metadata->mCommonMetadata.autoIncrement() = !!nextAutoIncrementId;
|
|
metadata->mNextAutoIncrementId = nextAutoIncrementId;
|
|
metadata->mCommittedAutoIncrementId = nextAutoIncrementId;
|
|
|
|
if (NS_WARN_IF(!objectStores.Put(objectStoreId, metadata, fallible))) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
lastObjectStoreId = std::max(lastObjectStoreId, objectStoreId);
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
usedIds.reset();
|
|
usedNames.reset();
|
|
|
|
// Load index information
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT "
|
|
"id, object_store_id, name, key_path, unique_index, multientry, "
|
|
"locale, is_auto_locale "
|
|
"FROM object_store_index"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
int64_t lastIndexId = 0;
|
|
|
|
while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
|
|
int64_t objectStoreId;
|
|
rv = stmt->GetInt64(1, &objectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> objectStoreMetadata;
|
|
if (NS_WARN_IF(!objectStores.Get(objectStoreId,
|
|
getter_AddRefs(objectStoreMetadata)))) {
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
MOZ_ASSERT(objectStoreMetadata->mCommonMetadata.id() == objectStoreId);
|
|
|
|
int64_t indexId;
|
|
rv = stmt->GetInt64(0, &indexId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!usedIds) {
|
|
usedIds.emplace();
|
|
}
|
|
|
|
if (NS_WARN_IF(indexId <= 0) ||
|
|
NS_WARN_IF(usedIds.ref().Contains(indexId))) {
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
if (NS_WARN_IF(!usedIds.ref().PutEntry(indexId, fallible))) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
nsString name;
|
|
rv = stmt->GetString(2, name);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsAutoString hashName;
|
|
hashName.AppendInt(indexId);
|
|
hashName.Append(':');
|
|
hashName.Append(name);
|
|
|
|
if (!usedNames) {
|
|
usedNames.emplace();
|
|
}
|
|
|
|
if (NS_WARN_IF(usedNames.ref().Contains(hashName))) {
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
if (NS_WARN_IF(!usedNames.ref().PutEntry(hashName, fallible))) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
RefPtr<FullIndexMetadata> indexMetadata = new FullIndexMetadata();
|
|
indexMetadata->mCommonMetadata.id() = indexId;
|
|
indexMetadata->mCommonMetadata.name() = name;
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
int32_t columnType;
|
|
rv = stmt->GetTypeOfIndex(3, &columnType);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
MOZ_ASSERT(columnType != mozIStorageStatement::VALUE_TYPE_NULL);
|
|
}
|
|
#endif
|
|
|
|
nsString keyPathSerialization;
|
|
rv = stmt->GetString(3, keyPathSerialization);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
indexMetadata->mCommonMetadata.keyPath() =
|
|
KeyPath::DeserializeFromString(keyPathSerialization);
|
|
if (NS_WARN_IF(!indexMetadata->mCommonMetadata.keyPath().IsValid())) {
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
int32_t scratch;
|
|
rv = stmt->GetInt32(4, &scratch);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
indexMetadata->mCommonMetadata.unique() = !!scratch;
|
|
|
|
rv = stmt->GetInt32(5, &scratch);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
indexMetadata->mCommonMetadata.multiEntry() = !!scratch;
|
|
|
|
#ifdef ENABLE_INTL_API
|
|
const bool localeAware = !stmt->IsNull(6);
|
|
if (localeAware) {
|
|
rv = stmt->GetUTF8String(6, indexMetadata->mCommonMetadata.locale());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->GetInt32(7, &scratch);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
indexMetadata->mCommonMetadata.autoLocale() = !!scratch;
|
|
|
|
// Update locale-aware indexes if necessary
|
|
const nsCString& indexedLocale = indexMetadata->mCommonMetadata.locale();
|
|
const bool& isAutoLocale = indexMetadata->mCommonMetadata.autoLocale();
|
|
nsCString systemLocale = IndexedDatabaseManager::GetLocale();
|
|
if (!systemLocale.IsEmpty() &&
|
|
isAutoLocale &&
|
|
!indexedLocale.EqualsASCII(systemLocale.get())) {
|
|
rv = UpdateLocaleAwareIndex(aConnection,
|
|
indexMetadata->mCommonMetadata,
|
|
systemLocale);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (NS_WARN_IF(!objectStoreMetadata->mIndexes.Put(indexId, indexMetadata,
|
|
fallible))) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
lastIndexId = std::max(lastIndexId, indexId);
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(lastObjectStoreId == INT64_MAX) ||
|
|
NS_WARN_IF(lastIndexId == INT64_MAX)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
mMetadata->mNextObjectStoreId = lastObjectStoreId + 1;
|
|
mMetadata->mNextIndexId = lastIndexId + 1;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
#ifdef ENABLE_INTL_API
|
|
/* static */
|
|
nsresult
|
|
OpenDatabaseOp::UpdateLocaleAwareIndex(mozIStorageConnection* aConnection,
|
|
const IndexMetadata& aIndexMetadata,
|
|
const nsCString& aLocale)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsCString indexTable;
|
|
if (aIndexMetadata.unique()) {
|
|
indexTable.AssignLiteral("unique_index_data");
|
|
}
|
|
else {
|
|
indexTable.AssignLiteral("index_data");
|
|
}
|
|
|
|
nsCString readQuery = NS_LITERAL_CSTRING("SELECT value, object_data_key FROM ") +
|
|
indexTable +
|
|
NS_LITERAL_CSTRING(" WHERE index_id = :index_id");
|
|
nsCOMPtr<mozIStorageStatement> readStmt;
|
|
rv = aConnection->CreateStatement(readQuery, getter_AddRefs(readStmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = readStmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
|
|
aIndexMetadata.id());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageStatement> writeStmt;
|
|
bool needCreateWriteQuery = true;
|
|
bool hasResult;
|
|
while (NS_SUCCEEDED((rv = readStmt->ExecuteStep(&hasResult))) && hasResult) {
|
|
if (needCreateWriteQuery) {
|
|
needCreateWriteQuery = false;
|
|
nsCString writeQuery = NS_LITERAL_CSTRING("UPDATE ") + indexTable +
|
|
NS_LITERAL_CSTRING("SET value_locale = :value_locale "
|
|
"WHERE index_id = :index_id AND "
|
|
"value = :value AND "
|
|
"object_data_key = :object_data_key");
|
|
rv = aConnection->CreateStatement(writeQuery, getter_AddRefs(writeStmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
mozStorageStatementScoper scoper(writeStmt);
|
|
rv = writeStmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
|
|
aIndexMetadata.id());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
Key oldKey, newSortKey, objectKey;
|
|
rv = oldKey.SetFromStatement(readStmt, 0);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = oldKey.BindToStatement(writeStmt, NS_LITERAL_CSTRING("value"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = oldKey.ToLocaleBasedKey(newSortKey, aLocale);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = newSortKey.BindToStatement(writeStmt,
|
|
NS_LITERAL_CSTRING("value_locale"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = objectKey.SetFromStatement(readStmt, 1);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = objectKey.BindToStatement(writeStmt,
|
|
NS_LITERAL_CSTRING("object_data_key"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = writeStmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
nsCString metaQuery = NS_LITERAL_CSTRING("UPDATE object_store_index SET "
|
|
"locale = :locale WHERE id = :id");
|
|
nsCOMPtr<mozIStorageStatement> metaStmt;
|
|
rv = aConnection->CreateStatement(metaQuery, getter_AddRefs(metaStmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsString locale;
|
|
locale.AssignWithConversion(aLocale);
|
|
rv = metaStmt->BindStringByName(NS_LITERAL_CSTRING("locale"), locale);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = metaStmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), aIndexMetadata.id());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = metaStmt->Execute();
|
|
return rv;
|
|
}
|
|
#endif
|
|
|
|
nsresult
|
|
OpenDatabaseOp::BeginVersionChange()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::BeginVersionChange);
|
|
MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
|
|
MOZ_ASSERT(mMetadata->mCommonMetadata.version() <= mRequestedVersion);
|
|
MOZ_ASSERT(!mDatabase);
|
|
MOZ_ASSERT(!mVersionChangeTransaction);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
|
|
IsActorDestroyed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
EnsureDatabaseActor();
|
|
|
|
if (mDatabase->IsInvalidated()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
MOZ_ASSERT(!mDatabase->IsClosed());
|
|
|
|
DatabaseActorInfo* info;
|
|
MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info));
|
|
|
|
MOZ_ASSERT(info->mLiveDatabases.Contains(mDatabase));
|
|
MOZ_ASSERT(!info->mWaitingFactoryOp);
|
|
MOZ_ASSERT(info->mMetadata == mMetadata);
|
|
|
|
RefPtr<VersionChangeTransaction> transaction =
|
|
new VersionChangeTransaction(this);
|
|
|
|
if (NS_WARN_IF(!transaction->CopyDatabaseMetadata())) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
MOZ_ASSERT(info->mMetadata != mMetadata);
|
|
mMetadata = info->mMetadata;
|
|
|
|
NullableVersion newVersion = mRequestedVersion;
|
|
|
|
nsresult rv =
|
|
SendVersionChangeMessages(info,
|
|
mDatabase,
|
|
mMetadata->mCommonMetadata.version(),
|
|
newVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mVersionChangeTransaction.swap(transaction);
|
|
|
|
if (mMaybeBlockedDatabases.IsEmpty()) {
|
|
// We don't need to wait on any databases, just jump to the transaction
|
|
// pool.
|
|
WaitForTransactions();
|
|
return NS_OK;
|
|
}
|
|
|
|
info->mWaitingFactoryOp = this;
|
|
|
|
mState = State::WaitingForOtherDatabasesToClose;
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
OpenDatabaseOp::NoteDatabaseClosed(Database* aDatabase)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(aDatabase);
|
|
MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose ||
|
|
mState == State::WaitingForTransactionsToComplete ||
|
|
mState == State::DatabaseWorkVersionChange);
|
|
|
|
if (mState != State::WaitingForOtherDatabasesToClose) {
|
|
MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
|
|
MOZ_ASSERT(mRequestedVersion >
|
|
aDatabase->Metadata()->mCommonMetadata.version(),
|
|
"Must only be closing databases for a previous version!");
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
|
|
|
|
bool actorDestroyed = IsActorDestroyed() || mDatabase->IsActorDestroyed();
|
|
|
|
nsresult rv;
|
|
if (actorDestroyed) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
} else {
|
|
rv = NS_OK;
|
|
}
|
|
|
|
if (mMaybeBlockedDatabases.RemoveElement(aDatabase) &&
|
|
mMaybeBlockedDatabases.IsEmpty()) {
|
|
if (actorDestroyed) {
|
|
DatabaseActorInfo* info;
|
|
MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info));
|
|
MOZ_ASSERT(info->mWaitingFactoryOp == this);
|
|
info->mWaitingFactoryOp = nullptr;
|
|
} else {
|
|
WaitForTransactions();
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = rv;
|
|
}
|
|
|
|
mState = State::SendingResults;
|
|
MOZ_ALWAYS_SUCCEEDS(Run());
|
|
}
|
|
}
|
|
|
|
void
|
|
OpenDatabaseOp::SendBlockedNotification()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
|
|
|
|
if (!IsActorDestroyed()) {
|
|
Unused << SendBlocked(mMetadata->mCommonMetadata.version());
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
OpenDatabaseOp::DispatchToWorkThread()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::WaitingForTransactionsToComplete);
|
|
MOZ_ASSERT(mVersionChangeTransaction);
|
|
MOZ_ASSERT(mVersionChangeTransaction->GetMode() ==
|
|
IDBTransaction::VERSION_CHANGE);
|
|
MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
|
|
IsActorDestroyed() ||
|
|
mDatabase->IsInvalidated()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
mState = State::DatabaseWorkVersionChange;
|
|
|
|
// Intentionally empty.
|
|
nsTArray<nsString> objectStoreNames;
|
|
|
|
const int64_t loggingSerialNumber =
|
|
mVersionChangeTransaction->LoggingSerialNumber();
|
|
const nsID& backgroundChildLoggingId =
|
|
mVersionChangeTransaction->GetLoggingInfo()->Id();
|
|
|
|
if (NS_WARN_IF(!mDatabase->RegisterTransaction(mVersionChangeTransaction))) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
if (!gConnectionPool) {
|
|
gConnectionPool = new ConnectionPool();
|
|
}
|
|
|
|
RefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this);
|
|
|
|
uint64_t transactionId =
|
|
versionChangeOp->StartOnConnectionPool(
|
|
backgroundChildLoggingId,
|
|
mVersionChangeTransaction->DatabaseId(),
|
|
loggingSerialNumber,
|
|
objectStoreNames,
|
|
/* aIsWriteTransaction */ true);
|
|
|
|
mVersionChangeOp = versionChangeOp;
|
|
|
|
mVersionChangeTransaction->NoteActiveRequest();
|
|
mVersionChangeTransaction->SetActive(transactionId);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
OpenDatabaseOp::SendUpgradeNeeded()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::DatabaseWorkVersionChange);
|
|
MOZ_ASSERT(mVersionChangeTransaction);
|
|
MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
|
|
MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
|
|
MOZ_ASSERT_IF(!IsActorDestroyed(), mDatabase);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
|
|
IsActorDestroyed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
RefPtr<VersionChangeTransaction> transaction;
|
|
mVersionChangeTransaction.swap(transaction);
|
|
|
|
nsresult rv = EnsureDatabaseActorIsAlive();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Transfer ownership to IPDL.
|
|
transaction->SetActorAlive();
|
|
|
|
if (!mDatabase->SendPBackgroundIDBVersionChangeTransactionConstructor(
|
|
transaction,
|
|
mMetadata->mCommonMetadata.version(),
|
|
mRequestedVersion,
|
|
mMetadata->mNextObjectStoreId,
|
|
mMetadata->mNextIndexId)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
OpenDatabaseOp::SendResults()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::SendingResults);
|
|
MOZ_ASSERT_IF(NS_SUCCEEDED(mResultCode), mMaybeBlockedDatabases.IsEmpty());
|
|
MOZ_ASSERT_IF(NS_SUCCEEDED(mResultCode), !mVersionChangeTransaction);
|
|
|
|
mMaybeBlockedDatabases.Clear();
|
|
|
|
// Only needed if we're being called from within NoteDatabaseDone() since this
|
|
// OpenDatabaseOp is only held alive by the gLiveDatabaseHashtable.
|
|
RefPtr<OpenDatabaseOp> kungFuDeathGrip;
|
|
|
|
DatabaseActorInfo* info;
|
|
if (gLiveDatabaseHashtable &&
|
|
gLiveDatabaseHashtable->Get(mDatabaseId, &info) &&
|
|
info->mWaitingFactoryOp) {
|
|
MOZ_ASSERT(info->mWaitingFactoryOp == this);
|
|
kungFuDeathGrip =
|
|
static_cast<OpenDatabaseOp*>(info->mWaitingFactoryOp.get());
|
|
info->mWaitingFactoryOp = nullptr;
|
|
}
|
|
|
|
if (mVersionChangeTransaction) {
|
|
MOZ_ASSERT(NS_FAILED(mResultCode));
|
|
|
|
mVersionChangeTransaction->Abort(mResultCode, /* aForce */ true);
|
|
mVersionChangeTransaction = nullptr;
|
|
}
|
|
|
|
if (IsActorDestroyed()) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
} else {
|
|
FactoryRequestResponse response;
|
|
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
// If we just successfully completed a versionchange operation then we
|
|
// need to update the version in our metadata.
|
|
mMetadata->mCommonMetadata.version() = mRequestedVersion;
|
|
|
|
nsresult rv = EnsureDatabaseActorIsAlive();
|
|
if (NS_SUCCEEDED(rv)) {
|
|
// We successfully opened a database so use its actor as the success
|
|
// result for this request.
|
|
OpenDatabaseRequestResponse openResponse;
|
|
openResponse.databaseParent() = mDatabase;
|
|
response = openResponse;
|
|
} else {
|
|
response = ClampResultCode(rv);
|
|
#ifdef DEBUG
|
|
mResultCode = response.get_nsresult();
|
|
#endif
|
|
}
|
|
} else {
|
|
#ifdef DEBUG
|
|
// If something failed then our metadata pointer is now bad. No one should
|
|
// ever touch it again though so just null it out in DEBUG builds to make
|
|
// sure we find such cases.
|
|
mMetadata = nullptr;
|
|
#endif
|
|
response = ClampResultCode(mResultCode);
|
|
}
|
|
|
|
Unused <<
|
|
PBackgroundIDBFactoryRequestParent::Send__delete__(this, response);
|
|
}
|
|
|
|
if (mDatabase) {
|
|
MOZ_ASSERT(!mDirectoryLock);
|
|
|
|
if (NS_FAILED(mResultCode)) {
|
|
mDatabase->Invalidate();
|
|
}
|
|
|
|
// Make sure to release the database on this thread.
|
|
mDatabase = nullptr;
|
|
} else if (mDirectoryLock) {
|
|
nsCOMPtr<nsIRunnable> callback =
|
|
NewRunnableMethod(this, &OpenDatabaseOp::ConnectionClosedCallback);
|
|
|
|
RefPtr<WaitForTransactionsHelper> helper =
|
|
new WaitForTransactionsHelper(mDatabaseId, callback);
|
|
helper->WaitForTransactions();
|
|
}
|
|
|
|
FinishSendResults();
|
|
}
|
|
|
|
void
|
|
OpenDatabaseOp::ConnectionClosedCallback()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(NS_FAILED(mResultCode));
|
|
MOZ_ASSERT(mDirectoryLock);
|
|
|
|
mDirectoryLock = nullptr;
|
|
}
|
|
|
|
void
|
|
OpenDatabaseOp::EnsureDatabaseActor()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::BeginVersionChange ||
|
|
mState == State::DatabaseWorkVersionChange ||
|
|
mState == State::SendingResults);
|
|
MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
|
|
MOZ_ASSERT(!mDatabaseFilePath.IsEmpty());
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
if (mDatabase) {
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(mMetadata->mDatabaseId.IsEmpty());
|
|
mMetadata->mDatabaseId = mDatabaseId;
|
|
|
|
MOZ_ASSERT(mMetadata->mFilePath.IsEmpty());
|
|
mMetadata->mFilePath = mDatabaseFilePath;
|
|
|
|
DatabaseActorInfo* info;
|
|
if (gLiveDatabaseHashtable->Get(mDatabaseId, &info)) {
|
|
AssertMetadataConsistency(info->mMetadata);
|
|
mMetadata = info->mMetadata;
|
|
}
|
|
|
|
auto factory = static_cast<Factory*>(Manager());
|
|
|
|
mDatabase = new Database(factory,
|
|
mCommonParams.principalInfo(),
|
|
mOptionalContentParentId,
|
|
mGroup,
|
|
mOrigin,
|
|
mTelemetryId,
|
|
mMetadata,
|
|
mFileManager,
|
|
mDirectoryLock.forget(),
|
|
mFileHandleDisabled,
|
|
mChromeWriteAccessAllowed);
|
|
|
|
if (info) {
|
|
info->mLiveDatabases.AppendElement(mDatabase);
|
|
} else {
|
|
info = new DatabaseActorInfo(mMetadata, mDatabase);
|
|
gLiveDatabaseHashtable->Put(mDatabaseId, info);
|
|
}
|
|
|
|
// Balanced in Database::CleanupMetadata().
|
|
IncreaseBusyCount();
|
|
}
|
|
|
|
nsresult
|
|
OpenDatabaseOp::EnsureDatabaseActorIsAlive()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::DatabaseWorkVersionChange ||
|
|
mState == State::SendingResults);
|
|
MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
EnsureDatabaseActor();
|
|
|
|
if (mDatabase->IsActorAlive()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
auto factory = static_cast<Factory*>(Manager());
|
|
|
|
DatabaseSpec spec;
|
|
MetadataToSpec(spec);
|
|
|
|
// Transfer ownership to IPDL.
|
|
mDatabase->SetActorAlive();
|
|
|
|
if (!factory->SendPBackgroundIDBDatabaseConstructor(mDatabase, spec, this)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
OpenDatabaseOp::MetadataToSpec(DatabaseSpec& aSpec)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mMetadata);
|
|
|
|
aSpec.metadata() = mMetadata->mCommonMetadata;
|
|
|
|
for (auto objectStoreIter = mMetadata->mObjectStores.ConstIter();
|
|
!objectStoreIter.Done();
|
|
objectStoreIter.Next()) {
|
|
FullObjectStoreMetadata* metadata = objectStoreIter.UserData();
|
|
MOZ_ASSERT(objectStoreIter.Key());
|
|
MOZ_ASSERT(metadata);
|
|
|
|
// XXX This should really be fallible...
|
|
ObjectStoreSpec* objectStoreSpec = aSpec.objectStores().AppendElement();
|
|
objectStoreSpec->metadata() = metadata->mCommonMetadata;
|
|
|
|
for (auto indexIter = metadata->mIndexes.Iter();
|
|
!indexIter.Done();
|
|
indexIter.Next()) {
|
|
FullIndexMetadata* indexMetadata = indexIter.UserData();
|
|
MOZ_ASSERT(indexIter.Key());
|
|
MOZ_ASSERT(indexMetadata);
|
|
|
|
// XXX This should really be fallible...
|
|
IndexMetadata* metadata = objectStoreSpec->indexes().AppendElement();
|
|
*metadata = indexMetadata->mCommonMetadata;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
void
|
|
OpenDatabaseOp::AssertMetadataConsistency(const FullDatabaseMetadata* aMetadata)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
const FullDatabaseMetadata* thisDB = mMetadata;
|
|
const FullDatabaseMetadata* otherDB = aMetadata;
|
|
|
|
MOZ_ASSERT(thisDB);
|
|
MOZ_ASSERT(otherDB);
|
|
MOZ_ASSERT(thisDB != otherDB);
|
|
|
|
MOZ_ASSERT(thisDB->mCommonMetadata.name() == otherDB->mCommonMetadata.name());
|
|
MOZ_ASSERT(thisDB->mCommonMetadata.version() ==
|
|
otherDB->mCommonMetadata.version());
|
|
MOZ_ASSERT(thisDB->mCommonMetadata.persistenceType() ==
|
|
otherDB->mCommonMetadata.persistenceType());
|
|
MOZ_ASSERT(thisDB->mDatabaseId == otherDB->mDatabaseId);
|
|
MOZ_ASSERT(thisDB->mFilePath == otherDB->mFilePath);
|
|
|
|
// |thisDB| reflects the latest objectStore and index ids that have committed
|
|
// to disk. The in-memory metadata |otherDB| keeps track of objectStores and
|
|
// indexes that were created and then removed as well, so the next ids for
|
|
// |otherDB| may be higher than for |thisDB|.
|
|
MOZ_ASSERT(thisDB->mNextObjectStoreId <= otherDB->mNextObjectStoreId);
|
|
MOZ_ASSERT(thisDB->mNextIndexId <= otherDB->mNextIndexId);
|
|
|
|
MOZ_ASSERT(thisDB->mObjectStores.Count() == otherDB->mObjectStores.Count());
|
|
|
|
for (auto objectStoreIter = thisDB->mObjectStores.ConstIter();
|
|
!objectStoreIter.Done();
|
|
objectStoreIter.Next()) {
|
|
FullObjectStoreMetadata* thisObjectStore = objectStoreIter.UserData();
|
|
MOZ_ASSERT(thisObjectStore);
|
|
MOZ_ASSERT(!thisObjectStore->mDeleted);
|
|
|
|
auto* otherObjectStore =
|
|
MetadataNameOrIdMatcher<FullObjectStoreMetadata>::Match(
|
|
otherDB->mObjectStores, thisObjectStore->mCommonMetadata.id());
|
|
MOZ_ASSERT(otherObjectStore);
|
|
|
|
MOZ_ASSERT(thisObjectStore != otherObjectStore);
|
|
|
|
MOZ_ASSERT(thisObjectStore->mCommonMetadata.id() ==
|
|
otherObjectStore->mCommonMetadata.id());
|
|
MOZ_ASSERT(thisObjectStore->mCommonMetadata.name() ==
|
|
otherObjectStore->mCommonMetadata.name());
|
|
MOZ_ASSERT(thisObjectStore->mCommonMetadata.autoIncrement() ==
|
|
otherObjectStore->mCommonMetadata.autoIncrement());
|
|
MOZ_ASSERT(thisObjectStore->mCommonMetadata.keyPath() ==
|
|
otherObjectStore->mCommonMetadata.keyPath());
|
|
// mNextAutoIncrementId and mCommittedAutoIncrementId may be modified
|
|
// concurrently with this OpenOp, so it is not possible to assert equality
|
|
// here. It's also possible that we've written the new ids to disk but not
|
|
// yet updated the in-memory count.
|
|
MOZ_ASSERT(thisObjectStore->mNextAutoIncrementId <=
|
|
otherObjectStore->mNextAutoIncrementId);
|
|
MOZ_ASSERT(thisObjectStore->mCommittedAutoIncrementId <=
|
|
otherObjectStore->mCommittedAutoIncrementId ||
|
|
thisObjectStore->mCommittedAutoIncrementId ==
|
|
otherObjectStore->mNextAutoIncrementId);
|
|
MOZ_ASSERT(!otherObjectStore->mDeleted);
|
|
|
|
MOZ_ASSERT(thisObjectStore->mIndexes.Count() ==
|
|
otherObjectStore->mIndexes.Count());
|
|
|
|
for (auto indexIter = thisObjectStore->mIndexes.Iter();
|
|
!indexIter.Done();
|
|
indexIter.Next()) {
|
|
FullIndexMetadata* thisIndex = indexIter.UserData();
|
|
MOZ_ASSERT(thisIndex);
|
|
MOZ_ASSERT(!thisIndex->mDeleted);
|
|
|
|
auto* otherIndex =
|
|
MetadataNameOrIdMatcher<FullIndexMetadata>::
|
|
Match(otherObjectStore->mIndexes, thisIndex->mCommonMetadata.id());
|
|
MOZ_ASSERT(otherIndex);
|
|
|
|
MOZ_ASSERT(thisIndex != otherIndex);
|
|
|
|
MOZ_ASSERT(thisIndex->mCommonMetadata.id() ==
|
|
otherIndex->mCommonMetadata.id());
|
|
MOZ_ASSERT(thisIndex->mCommonMetadata.name() ==
|
|
otherIndex->mCommonMetadata.name());
|
|
MOZ_ASSERT(thisIndex->mCommonMetadata.keyPath() ==
|
|
otherIndex->mCommonMetadata.keyPath());
|
|
MOZ_ASSERT(thisIndex->mCommonMetadata.unique() ==
|
|
otherIndex->mCommonMetadata.unique());
|
|
MOZ_ASSERT(thisIndex->mCommonMetadata.multiEntry() ==
|
|
otherIndex->mCommonMetadata.multiEntry());
|
|
MOZ_ASSERT(!otherIndex->mDeleted);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // DEBUG
|
|
|
|
nsresult
|
|
OpenDatabaseOp::
|
|
VersionChangeOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mOpenDatabaseOp);
|
|
MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
!OperationMayProceed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"OpenDatabaseOp::VersionChangeOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld]: "
|
|
"Beginning database work",
|
|
"IndexedDB %s: P T[%lld]: DB Start",
|
|
IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
|
|
mLoggingSerialNumber);
|
|
|
|
Transaction()->SetActiveOnConnectionThread();
|
|
|
|
nsresult rv = aConnection->BeginWriteTransaction();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
DatabaseConnection::CachedStatement updateStmt;
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"UPDATE database "
|
|
"SET version = :version;"),
|
|
&updateStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("version"),
|
|
int64_t(mRequestedVersion));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = updateStmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
OpenDatabaseOp::
|
|
VersionChangeOp::SendSuccessResult()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mOpenDatabaseOp);
|
|
MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
|
|
MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this);
|
|
|
|
nsresult rv = mOpenDatabaseOp->SendUpgradeNeeded();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
OpenDatabaseOp::
|
|
VersionChangeOp::SendFailureResult(nsresult aResultCode)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mOpenDatabaseOp);
|
|
MOZ_ASSERT(mOpenDatabaseOp->mState == State::DatabaseWorkVersionChange);
|
|
MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this);
|
|
|
|
mOpenDatabaseOp->SetFailureCode(aResultCode);
|
|
mOpenDatabaseOp->mState = State::SendingResults;
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(mOpenDatabaseOp->Run());
|
|
|
|
return false;
|
|
}
|
|
|
|
void
|
|
OpenDatabaseOp::
|
|
VersionChangeOp::Cleanup()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mOpenDatabaseOp);
|
|
MOZ_ASSERT(mOpenDatabaseOp->mVersionChangeOp == this);
|
|
|
|
mOpenDatabaseOp->mVersionChangeOp = nullptr;
|
|
mOpenDatabaseOp = nullptr;
|
|
|
|
#ifdef DEBUG
|
|
// A bit hacky but the VersionChangeOp is not generated in response to a
|
|
// child request like most other database operations. Do this to make our
|
|
// assertions happy.
|
|
NoteActorDestroyed();
|
|
#endif
|
|
|
|
TransactionDatabaseOperationBase::Cleanup();
|
|
}
|
|
|
|
void
|
|
DeleteDatabaseOp::LoadPreviousVersion(nsIFile* aDatabaseFile)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aDatabaseFile);
|
|
MOZ_ASSERT(mState == State::DatabaseWorkOpen);
|
|
MOZ_ASSERT(!mPreviousVersion);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DeleteDatabaseOp::LoadPreviousVersion",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<mozIStorageService> ss =
|
|
do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageConnection> connection;
|
|
rv = OpenDatabaseAndHandleBusy(ss, aDatabaseFile, getter_AddRefs(connection));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
connection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT name "
|
|
"FROM database"
|
|
), getter_AddRefs(stmt)));
|
|
|
|
bool hasResult;
|
|
MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
|
|
|
|
nsString databaseName;
|
|
MOZ_ALWAYS_SUCCEEDS(stmt->GetString(0, databaseName));
|
|
|
|
MOZ_ASSERT(mCommonParams.metadata().name() == databaseName);
|
|
}
|
|
#endif
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = connection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT version "
|
|
"FROM database"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
bool hasResult;
|
|
rv = stmt->ExecuteStep(&hasResult);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!hasResult)) {
|
|
return;
|
|
}
|
|
|
|
int64_t version;
|
|
rv = stmt->GetInt64(0, &version);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
mPreviousVersion = uint64_t(version);
|
|
}
|
|
|
|
nsresult
|
|
DeleteDatabaseOp::DatabaseOpen()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::DatabaseOpenPending);
|
|
|
|
// Swap this to the stack now to ensure that we release it on this thread.
|
|
RefPtr<ContentParent> contentParent;
|
|
mContentParent.swap(contentParent);
|
|
|
|
nsresult rv = SendToIOThread();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DeleteDatabaseOp::DoDatabaseWork()
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(mState == State::DatabaseWorkOpen);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DeleteDatabaseOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
!OperationMayProceed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
const nsString& databaseName = mCommonParams.metadata().name();
|
|
PersistenceType persistenceType = mCommonParams.metadata().persistenceType();
|
|
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
nsCOMPtr<nsIFile> directory;
|
|
nsresult rv = quotaManager->GetDirectoryForOrigin(persistenceType,
|
|
mOrigin,
|
|
getter_AddRefs(directory));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = directory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = directory->GetPath(mDatabaseDirectoryPath);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsAutoString filename;
|
|
GetDatabaseFilename(databaseName, filename);
|
|
|
|
mDatabaseFilenameBase = filename;
|
|
|
|
nsCOMPtr<nsIFile> dbFile;
|
|
rv = directory->Clone(getter_AddRefs(dbFile));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = dbFile->Append(filename + NS_LITERAL_STRING(".sqlite"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
nsString databaseFilePath;
|
|
rv = dbFile->GetPath(databaseFilePath);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(databaseFilePath == mDatabaseFilePath);
|
|
#endif
|
|
|
|
bool exists;
|
|
rv = dbFile->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (exists) {
|
|
// Parts of this function may fail but that shouldn't prevent us from
|
|
// deleting the file eventually.
|
|
LoadPreviousVersion(dbFile);
|
|
|
|
mState = State::BeginVersionChange;
|
|
} else {
|
|
mState = State::SendingResults;
|
|
}
|
|
|
|
rv = mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DeleteDatabaseOp::BeginVersionChange()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::BeginVersionChange);
|
|
MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
|
|
IsActorDestroyed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
DatabaseActorInfo* info;
|
|
if (gLiveDatabaseHashtable->Get(mDatabaseId, &info)) {
|
|
MOZ_ASSERT(!info->mWaitingFactoryOp);
|
|
|
|
NullableVersion newVersion = null_t();
|
|
|
|
nsresult rv =
|
|
SendVersionChangeMessages(info, nullptr, mPreviousVersion, newVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!mMaybeBlockedDatabases.IsEmpty()) {
|
|
info->mWaitingFactoryOp = this;
|
|
|
|
mState = State::WaitingForOtherDatabasesToClose;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// No other databases need to be notified, just make sure that all
|
|
// transactions are complete.
|
|
WaitForTransactions();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DeleteDatabaseOp::DispatchToWorkThread()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::WaitingForTransactionsToComplete);
|
|
MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
|
|
IsActorDestroyed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
mState = State::DatabaseWorkVersionChange;
|
|
|
|
RefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this);
|
|
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
nsresult rv =
|
|
quotaManager->IOThread()->Dispatch(versionChangeOp.forget(),
|
|
NS_DISPATCH_NORMAL);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DeleteDatabaseOp::NoteDatabaseClosed(Database* aDatabase)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
|
|
MOZ_ASSERT(!mMaybeBlockedDatabases.IsEmpty());
|
|
|
|
bool actorDestroyed = IsActorDestroyed();
|
|
|
|
nsresult rv;
|
|
if (actorDestroyed) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
} else {
|
|
rv = NS_OK;
|
|
}
|
|
|
|
if (mMaybeBlockedDatabases.RemoveElement(aDatabase) &&
|
|
mMaybeBlockedDatabases.IsEmpty()) {
|
|
if (actorDestroyed) {
|
|
DatabaseActorInfo* info;
|
|
MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info));
|
|
MOZ_ASSERT(info->mWaitingFactoryOp == this);
|
|
info->mWaitingFactoryOp = nullptr;
|
|
} else {
|
|
WaitForTransactions();
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = rv;
|
|
}
|
|
|
|
mState = State::SendingResults;
|
|
MOZ_ALWAYS_SUCCEEDS(Run());
|
|
}
|
|
}
|
|
|
|
void
|
|
DeleteDatabaseOp::SendBlockedNotification()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::WaitingForOtherDatabasesToClose);
|
|
|
|
if (!IsActorDestroyed()) {
|
|
Unused << SendBlocked(mPreviousVersion);
|
|
}
|
|
}
|
|
|
|
void
|
|
DeleteDatabaseOp::SendResults()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::SendingResults);
|
|
|
|
if (!IsActorDestroyed()) {
|
|
FactoryRequestResponse response;
|
|
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
response = DeleteDatabaseRequestResponse(mPreviousVersion);
|
|
} else {
|
|
response = ClampResultCode(mResultCode);
|
|
}
|
|
|
|
Unused <<
|
|
PBackgroundIDBFactoryRequestParent::Send__delete__(this, response);
|
|
}
|
|
|
|
mDirectoryLock = nullptr;
|
|
|
|
FinishSendResults();
|
|
}
|
|
|
|
nsresult
|
|
DeleteDatabaseOp::
|
|
VersionChangeOp::DeleteFile(nsIFile* aDirectory,
|
|
const nsAString& aFilename,
|
|
QuotaManager* aQuotaManager)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aDirectory);
|
|
MOZ_ASSERT(!aFilename.IsEmpty());
|
|
MOZ_ASSERT_IF(aQuotaManager, mDeleteDatabaseOp->mEnforcingQuota);
|
|
|
|
MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DeleteDatabaseOp::VersionChangeOp::DeleteFile",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsCOMPtr<nsIFile> file;
|
|
nsresult rv = aDirectory->Clone(getter_AddRefs(file));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = file->Append(aFilename);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
int64_t fileSize;
|
|
|
|
if (aQuotaManager) {
|
|
rv = file->GetFileSize(&fileSize);
|
|
if (rv == NS_ERROR_FILE_NOT_FOUND ||
|
|
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(fileSize >= 0);
|
|
}
|
|
|
|
rv = file->Remove(false);
|
|
if (rv == NS_ERROR_FILE_NOT_FOUND ||
|
|
rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (aQuotaManager && fileSize > 0) {
|
|
const PersistenceType& persistenceType =
|
|
mDeleteDatabaseOp->mCommonParams.metadata().persistenceType();
|
|
|
|
aQuotaManager->DecreaseUsageForOrigin(persistenceType,
|
|
mDeleteDatabaseOp->mGroup,
|
|
mDeleteDatabaseOp->mOrigin,
|
|
fileSize);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DeleteDatabaseOp::
|
|
VersionChangeOp::RunOnIOThread()
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DeleteDatabaseOp::VersionChangeOp::RunOnIOThread",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
!OperationMayProceed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
const PersistenceType& persistenceType =
|
|
mDeleteDatabaseOp->mCommonParams.metadata().persistenceType();
|
|
|
|
QuotaManager* quotaManager =
|
|
mDeleteDatabaseOp->mEnforcingQuota ?
|
|
QuotaManager::Get() :
|
|
nullptr;
|
|
|
|
MOZ_ASSERT_IF(mDeleteDatabaseOp->mEnforcingQuota, quotaManager);
|
|
|
|
nsCOMPtr<nsIFile> directory =
|
|
GetFileForPath(mDeleteDatabaseOp->mDatabaseDirectoryPath);
|
|
if (NS_WARN_IF(!directory)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
// The database file counts towards quota.
|
|
nsAutoString filename =
|
|
mDeleteDatabaseOp->mDatabaseFilenameBase + NS_LITERAL_STRING(".sqlite");
|
|
|
|
nsresult rv = DeleteFile(directory, filename, quotaManager);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// .sqlite-journal files don't count towards quota.
|
|
const NS_ConvertASCIItoUTF16 journalSuffix(
|
|
kSQLiteJournalSuffix,
|
|
LiteralStringLength(kSQLiteJournalSuffix));
|
|
|
|
filename = mDeleteDatabaseOp->mDatabaseFilenameBase + journalSuffix;
|
|
|
|
rv = DeleteFile(directory, filename, /* doesn't count */ nullptr);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// .sqlite-shm files don't count towards quota.
|
|
const NS_ConvertASCIItoUTF16 shmSuffix(kSQLiteSHMSuffix,
|
|
LiteralStringLength(kSQLiteSHMSuffix));
|
|
|
|
filename = mDeleteDatabaseOp->mDatabaseFilenameBase + shmSuffix;
|
|
|
|
rv = DeleteFile(directory, filename, /* doesn't count */ nullptr);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// .sqlite-wal files do count towards quota.
|
|
const NS_ConvertASCIItoUTF16 walSuffix(kSQLiteWALSuffix,
|
|
LiteralStringLength(kSQLiteWALSuffix));
|
|
|
|
filename = mDeleteDatabaseOp->mDatabaseFilenameBase + walSuffix;
|
|
|
|
rv = DeleteFile(directory, filename, quotaManager);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> fmDirectory;
|
|
rv = directory->Clone(getter_AddRefs(fmDirectory));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// The files directory counts towards quota.
|
|
const NS_ConvertASCIItoUTF16 filesSuffix(
|
|
kFileManagerDirectoryNameSuffix,
|
|
LiteralStringLength(kFileManagerDirectoryNameSuffix));
|
|
|
|
rv = fmDirectory->Append(mDeleteDatabaseOp->mDatabaseFilenameBase +
|
|
filesSuffix);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool exists;
|
|
rv = fmDirectory->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (exists) {
|
|
bool isDirectory;
|
|
rv = fmDirectory->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!isDirectory)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
uint64_t usage = 0;
|
|
|
|
if (mDeleteDatabaseOp->mEnforcingQuota) {
|
|
rv = FileManager::GetUsage(fmDirectory, &usage);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = fmDirectory->Remove(true);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
// We may have deleted some files, check if we can and update quota
|
|
// information before returning the error.
|
|
if (mDeleteDatabaseOp->mEnforcingQuota) {
|
|
uint64_t newUsage;
|
|
if (NS_SUCCEEDED(FileManager::GetUsage(fmDirectory, &newUsage))) {
|
|
MOZ_ASSERT(newUsage <= usage);
|
|
usage = usage - newUsage;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mDeleteDatabaseOp->mEnforcingQuota && usage) {
|
|
quotaManager->DecreaseUsageForOrigin(persistenceType,
|
|
mDeleteDatabaseOp->mGroup,
|
|
mDeleteDatabaseOp->mOrigin,
|
|
usage);
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
|
|
MOZ_ASSERT(mgr);
|
|
|
|
const nsString& databaseName =
|
|
mDeleteDatabaseOp->mCommonParams.metadata().name();
|
|
|
|
mgr->InvalidateFileManager(persistenceType,
|
|
mDeleteDatabaseOp->mOrigin,
|
|
databaseName);
|
|
|
|
rv = mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DeleteDatabaseOp::
|
|
VersionChangeOp::RunOnOwningThread()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mDeleteDatabaseOp->mState == State::DatabaseWorkVersionChange);
|
|
|
|
RefPtr<DeleteDatabaseOp> deleteOp;
|
|
mDeleteDatabaseOp.swap(deleteOp);
|
|
|
|
if (deleteOp->IsActorDestroyed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
deleteOp->SetFailureCode(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
|
|
} else {
|
|
DatabaseActorInfo* info;
|
|
if (gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId, &info) &&
|
|
info->mWaitingFactoryOp) {
|
|
MOZ_ASSERT(info->mWaitingFactoryOp == deleteOp);
|
|
info->mWaitingFactoryOp = nullptr;
|
|
}
|
|
|
|
if (NS_FAILED(mResultCode)) {
|
|
if (NS_SUCCEEDED(deleteOp->ResultCode())) {
|
|
deleteOp->SetFailureCode(mResultCode);
|
|
}
|
|
} else {
|
|
// Inform all the other databases that they are now invalidated. That
|
|
// should remove the previous metadata from our table.
|
|
if (info) {
|
|
MOZ_ASSERT(!info->mLiveDatabases.IsEmpty());
|
|
|
|
FallibleTArray<Database*> liveDatabases;
|
|
if (NS_WARN_IF(!liveDatabases.AppendElements(info->mLiveDatabases,
|
|
fallible))) {
|
|
deleteOp->SetFailureCode(NS_ERROR_OUT_OF_MEMORY);
|
|
} else {
|
|
#ifdef DEBUG
|
|
// The code below should result in the deletion of |info|. Set to null
|
|
// here to make sure we find invalid uses later.
|
|
info = nullptr;
|
|
#endif
|
|
for (uint32_t count = liveDatabases.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
RefPtr<Database> database = liveDatabases[index];
|
|
database->Invalidate();
|
|
}
|
|
|
|
MOZ_ASSERT(!gLiveDatabaseHashtable->Get(deleteOp->mDatabaseId));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
deleteOp->mState = State::SendingResults;
|
|
MOZ_ALWAYS_SUCCEEDS(deleteOp->Run());
|
|
|
|
#ifdef DEBUG
|
|
// A bit hacky but the DeleteDatabaseOp::VersionChangeOp is not really a
|
|
// normal database operation that is tied to an actor. Do this to make our
|
|
// assertions happy.
|
|
NoteActorDestroyed();
|
|
#endif
|
|
}
|
|
|
|
nsresult
|
|
DeleteDatabaseOp::
|
|
VersionChangeOp::Run()
|
|
{
|
|
nsresult rv;
|
|
|
|
if (IsOnIOThread()) {
|
|
rv = RunOnIOThread();
|
|
} else {
|
|
RunOnOwningThread();
|
|
rv = NS_OK;
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = rv;
|
|
}
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
TransactionDatabaseOperationBase::TransactionDatabaseOperationBase(
|
|
TransactionBase* aTransaction)
|
|
: DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
|
|
aTransaction->GetLoggingInfo()->NextRequestSN())
|
|
, mTransaction(aTransaction)
|
|
, mTransactionLoggingSerialNumber(aTransaction->LoggingSerialNumber())
|
|
, mInternalState(InternalState::Initial)
|
|
, mTransactionIsAborted(aTransaction->IsAborted())
|
|
{
|
|
MOZ_ASSERT(aTransaction);
|
|
MOZ_ASSERT(LoggingSerialNumber());
|
|
}
|
|
|
|
TransactionDatabaseOperationBase::TransactionDatabaseOperationBase(
|
|
TransactionBase* aTransaction,
|
|
uint64_t aLoggingSerialNumber)
|
|
: DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
|
|
aLoggingSerialNumber)
|
|
, mTransaction(aTransaction)
|
|
, mTransactionLoggingSerialNumber(aTransaction->LoggingSerialNumber())
|
|
, mInternalState(InternalState::Initial)
|
|
, mTransactionIsAborted(aTransaction->IsAborted())
|
|
{
|
|
MOZ_ASSERT(aTransaction);
|
|
}
|
|
|
|
TransactionDatabaseOperationBase::~TransactionDatabaseOperationBase()
|
|
{
|
|
MOZ_ASSERT(mInternalState == InternalState::Completed);
|
|
MOZ_ASSERT(!mTransaction,
|
|
"TransactionDatabaseOperationBase::Cleanup() was not called by a "
|
|
"subclass!");
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
void
|
|
TransactionDatabaseOperationBase::AssertIsOnConnectionThread() const
|
|
{
|
|
MOZ_ASSERT(mTransaction);
|
|
mTransaction->AssertIsOnConnectionThread();
|
|
}
|
|
|
|
#endif // DEBUG
|
|
|
|
uint64_t
|
|
TransactionDatabaseOperationBase::StartOnConnectionPool(
|
|
const nsID& aBackgroundChildLoggingId,
|
|
const nsACString& aDatabaseId,
|
|
int64_t aLoggingSerialNumber,
|
|
const nsTArray<nsString>& aObjectStoreNames,
|
|
bool aIsWriteTransaction)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mInternalState == InternalState::Initial);
|
|
|
|
// Must set mInternalState before dispatching otherwise we will race with the
|
|
// connection thread.
|
|
mInternalState = InternalState::DatabaseWork;
|
|
|
|
return gConnectionPool->Start(aBackgroundChildLoggingId,
|
|
aDatabaseId,
|
|
aLoggingSerialNumber,
|
|
aObjectStoreNames,
|
|
aIsWriteTransaction,
|
|
this);
|
|
}
|
|
|
|
void
|
|
TransactionDatabaseOperationBase::DispatchToConnectionPool()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mInternalState == InternalState::Initial);
|
|
|
|
Unused << this->Run();
|
|
}
|
|
|
|
void
|
|
TransactionDatabaseOperationBase::RunOnConnectionThread()
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(mInternalState == InternalState::DatabaseWork);
|
|
MOZ_ASSERT(mTransaction);
|
|
MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"TransactionDatabaseOperationBase::RunOnConnectionThread",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// There are several cases where we don't actually have to to any work here.
|
|
|
|
if (mTransactionIsAborted || mTransaction->IsInvalidatedOnAnyThread()) {
|
|
// This transaction is already set to be aborted or invalidated.
|
|
mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
|
|
} else if (!OperationMayProceed()) {
|
|
// The operation was canceled in some way, likely because the child process
|
|
// has crashed.
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
} else {
|
|
Database* database = mTransaction->GetDatabase();
|
|
MOZ_ASSERT(database);
|
|
|
|
// Here we're actually going to perform the database operation.
|
|
nsresult rv = database->EnsureConnection();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
mResultCode = rv;
|
|
} else {
|
|
DatabaseConnection* connection = database->GetConnection();
|
|
MOZ_ASSERT(connection);
|
|
MOZ_ASSERT(connection->GetStorageConnection());
|
|
|
|
AutoSetProgressHandler autoProgress;
|
|
if (mLoggingSerialNumber) {
|
|
rv = autoProgress.Register(connection->GetStorageConnection(), this);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
mResultCode = rv;
|
|
}
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
if (mLoggingSerialNumber) {
|
|
IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld] Request[%llu]: "
|
|
"Beginning database work",
|
|
"IndexedDB %s: P T[%lld] R[%llu]: DB Start",
|
|
IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
|
|
mTransactionLoggingSerialNumber,
|
|
mLoggingSerialNumber);
|
|
}
|
|
|
|
rv = DoDatabaseWork(connection);
|
|
|
|
if (mLoggingSerialNumber) {
|
|
IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld] Request[%llu]: "
|
|
"Finished database work",
|
|
"IndexedDB %s: P T[%lld] R[%llu]: DB End",
|
|
IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
|
|
mTransactionLoggingSerialNumber,
|
|
mLoggingSerialNumber);
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
mResultCode = rv;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Must set mInternalState before dispatching otherwise we will race with the
|
|
// owning thread.
|
|
if (HasPreprocessInfo()) {
|
|
mInternalState = InternalState::SendingPreprocess;
|
|
} else {
|
|
mInternalState = InternalState::SendingResults;
|
|
}
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
}
|
|
|
|
bool
|
|
TransactionDatabaseOperationBase::HasPreprocessInfo()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
nsresult
|
|
TransactionDatabaseOperationBase::SendPreprocessInfo()
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
TransactionDatabaseOperationBase::NoteContinueReceived()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mInternalState == InternalState::WaitingForContinue);
|
|
|
|
mInternalState = InternalState::SendingResults;
|
|
|
|
Unused << this->Run();
|
|
}
|
|
|
|
void
|
|
TransactionDatabaseOperationBase::SendToConnectionPool()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mInternalState == InternalState::Initial);
|
|
|
|
// Must set mInternalState before dispatching otherwise we will race with the
|
|
// connection thread.
|
|
mInternalState = InternalState::DatabaseWork;
|
|
|
|
gConnectionPool->Dispatch(mTransaction->TransactionId(), this);
|
|
|
|
mTransaction->NoteActiveRequest();
|
|
}
|
|
|
|
void
|
|
TransactionDatabaseOperationBase::SendPreprocess()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess);
|
|
|
|
SendPreprocessInfoOrResults(/* aSendPreprocessInfo */ true);
|
|
}
|
|
|
|
void
|
|
TransactionDatabaseOperationBase::SendResults()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mInternalState == InternalState::SendingResults);
|
|
|
|
SendPreprocessInfoOrResults(/* aSendPreprocessInfo */ false);
|
|
}
|
|
|
|
void
|
|
TransactionDatabaseOperationBase::SendPreprocessInfoOrResults(
|
|
bool aSendPreprocessInfo)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mInternalState == InternalState::SendingPreprocess ||
|
|
mInternalState == InternalState::SendingResults);
|
|
MOZ_ASSERT(mTransaction);
|
|
|
|
// Only needed if we're being called from within NoteContinueReceived() since
|
|
// this TransactionDatabaseOperationBase is only held alive by the IPDL.
|
|
// SendSuccessResult/SendFailureResult releases that last reference.
|
|
RefPtr<TransactionDatabaseOperationBase> kungFuDeathGrip;
|
|
|
|
if (NS_WARN_IF(IsActorDestroyed())) {
|
|
// Don't send any notifications if the actor was destroyed already.
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
} else {
|
|
if (!aSendPreprocessInfo) {
|
|
kungFuDeathGrip = this;
|
|
}
|
|
|
|
if (mTransaction->IsInvalidated() || mTransaction->IsAborted()) {
|
|
// Aborted transactions always see their requests fail with ABORT_ERR,
|
|
// even if the request succeeded or failed with another error.
|
|
mResultCode = NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
|
|
} else if (NS_SUCCEEDED(mResultCode)) {
|
|
if (aSendPreprocessInfo) {
|
|
// This should not release the IPDL reference.
|
|
mResultCode = SendPreprocessInfo();
|
|
} else {
|
|
// This may release the IPDL reference.
|
|
mResultCode = SendSuccessResult();
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(mResultCode)) {
|
|
// This should definitely release the IPDL reference.
|
|
if (!SendFailureResult(mResultCode)) {
|
|
// Abort the transaction.
|
|
mTransaction->Abort(mResultCode, /* aForce */ false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aSendPreprocessInfo && NS_SUCCEEDED(mResultCode)) {
|
|
mInternalState = InternalState::WaitingForContinue;
|
|
} else {
|
|
if (mLoggingSerialNumber) {
|
|
mTransaction->NoteFinishedRequest();
|
|
}
|
|
|
|
Cleanup();
|
|
|
|
mInternalState = InternalState::Completed;
|
|
}
|
|
}
|
|
|
|
bool
|
|
TransactionDatabaseOperationBase::Init(TransactionBase* aTransaction)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mInternalState == InternalState::Initial);
|
|
MOZ_ASSERT(aTransaction);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
TransactionDatabaseOperationBase::Cleanup()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mInternalState == InternalState::SendingResults);
|
|
MOZ_ASSERT(mTransaction);
|
|
|
|
mTransaction = nullptr;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
TransactionDatabaseOperationBase::Run()
|
|
{
|
|
switch (mInternalState) {
|
|
case InternalState::Initial:
|
|
SendToConnectionPool();
|
|
return NS_OK;
|
|
|
|
case InternalState::DatabaseWork:
|
|
RunOnConnectionThread();
|
|
return NS_OK;
|
|
|
|
case InternalState::SendingPreprocess:
|
|
SendPreprocess();
|
|
return NS_OK;
|
|
|
|
case InternalState::SendingResults:
|
|
SendResults();
|
|
return NS_OK;
|
|
|
|
default:
|
|
MOZ_CRASH("Bad state!");
|
|
}
|
|
}
|
|
|
|
TransactionBase::
|
|
CommitOp::CommitOp(TransactionBase* aTransaction, nsresult aResultCode)
|
|
: DatabaseOperationBase(aTransaction->GetLoggingInfo()->Id(),
|
|
aTransaction->GetLoggingInfo()->NextRequestSN())
|
|
, mTransaction(aTransaction)
|
|
, mResultCode(aResultCode)
|
|
{
|
|
MOZ_ASSERT(aTransaction);
|
|
MOZ_ASSERT(LoggingSerialNumber());
|
|
}
|
|
|
|
nsresult
|
|
TransactionBase::
|
|
CommitOp::WriteAutoIncrementCounts()
|
|
{
|
|
MOZ_ASSERT(mTransaction);
|
|
mTransaction->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mTransaction->GetMode() == IDBTransaction::READ_WRITE ||
|
|
mTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH ||
|
|
mTransaction->GetMode() == IDBTransaction::CLEANUP ||
|
|
mTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
|
|
|
|
const nsTArray<RefPtr<FullObjectStoreMetadata>>& metadataArray =
|
|
mTransaction->mModifiedAutoIncrementObjectStoreMetadataArray;
|
|
|
|
if (!metadataArray.IsEmpty()) {
|
|
NS_NAMED_LITERAL_CSTRING(osid, "osid");
|
|
NS_NAMED_LITERAL_CSTRING(ai, "ai");
|
|
|
|
Database* database = mTransaction->GetDatabase();
|
|
MOZ_ASSERT(database);
|
|
|
|
DatabaseConnection* connection = database->GetConnection();
|
|
MOZ_ASSERT(connection);
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv;
|
|
|
|
for (uint32_t count = metadataArray.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
const RefPtr<FullObjectStoreMetadata>& metadata = metadataArray[index];
|
|
MOZ_ASSERT(!metadata->mDeleted);
|
|
MOZ_ASSERT(metadata->mNextAutoIncrementId > 1);
|
|
|
|
if (stmt) {
|
|
MOZ_ALWAYS_SUCCEEDS(stmt->Reset());
|
|
} else {
|
|
rv = connection->GetCachedStatement(
|
|
NS_LITERAL_CSTRING("UPDATE object_store "
|
|
"SET auto_increment = :") + ai +
|
|
NS_LITERAL_CSTRING(" WHERE id = :") + osid +
|
|
NS_LITERAL_CSTRING(";"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(osid, metadata->mCommonMetadata.id());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(ai, metadata->mNextAutoIncrementId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
TransactionBase::
|
|
CommitOp::CommitOrRollbackAutoIncrementCounts()
|
|
{
|
|
MOZ_ASSERT(mTransaction);
|
|
mTransaction->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mTransaction->GetMode() == IDBTransaction::READ_WRITE ||
|
|
mTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH ||
|
|
mTransaction->GetMode() == IDBTransaction::CLEANUP ||
|
|
mTransaction->GetMode() == IDBTransaction::VERSION_CHANGE);
|
|
|
|
nsTArray<RefPtr<FullObjectStoreMetadata>>& metadataArray =
|
|
mTransaction->mModifiedAutoIncrementObjectStoreMetadataArray;
|
|
|
|
if (!metadataArray.IsEmpty()) {
|
|
bool committed = NS_SUCCEEDED(mResultCode);
|
|
|
|
for (uint32_t count = metadataArray.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
RefPtr<FullObjectStoreMetadata>& metadata = metadataArray[index];
|
|
|
|
if (committed) {
|
|
metadata->mCommittedAutoIncrementId = metadata->mNextAutoIncrementId;
|
|
} else {
|
|
metadata->mNextAutoIncrementId = metadata->mCommittedAutoIncrementId;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
void
|
|
TransactionBase::
|
|
CommitOp::AssertForeignKeyConsistency(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
MOZ_ASSERT(mTransaction);
|
|
mTransaction->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mTransaction->GetMode() != IDBTransaction::READ_ONLY);
|
|
|
|
DatabaseConnection::CachedStatement pragmaStmt;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aConnection->GetCachedStatement(NS_LITERAL_CSTRING("PRAGMA foreign_keys;"),
|
|
&pragmaStmt));
|
|
|
|
bool hasResult;
|
|
MOZ_ALWAYS_SUCCEEDS(pragmaStmt->ExecuteStep(&hasResult));
|
|
|
|
MOZ_ASSERT(hasResult);
|
|
|
|
int32_t foreignKeysEnabled;
|
|
MOZ_ALWAYS_SUCCEEDS(pragmaStmt->GetInt32(0, &foreignKeysEnabled));
|
|
|
|
MOZ_ASSERT(foreignKeysEnabled, "Database doesn't have foreign keys enabled!");
|
|
|
|
DatabaseConnection::CachedStatement checkStmt;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aConnection->GetCachedStatement(
|
|
NS_LITERAL_CSTRING("PRAGMA foreign_key_check;"),
|
|
&checkStmt));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(checkStmt->ExecuteStep(&hasResult));
|
|
|
|
MOZ_ASSERT(!hasResult, "Database has inconsisistent foreign keys!");
|
|
}
|
|
|
|
#endif // DEBUG
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED0(TransactionBase::CommitOp, DatabaseOperationBase)
|
|
|
|
NS_IMETHODIMP
|
|
TransactionBase::
|
|
CommitOp::Run()
|
|
{
|
|
MOZ_ASSERT(mTransaction);
|
|
mTransaction->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"TransactionBase::CommitOp::Run",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld] Request[%llu]: "
|
|
"Beginning database work",
|
|
"IndexedDB %s: P T[%lld] R[%llu]: DB Start",
|
|
IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
|
|
mTransaction->LoggingSerialNumber(),
|
|
mLoggingSerialNumber);
|
|
|
|
if (mTransaction->GetMode() != IDBTransaction::READ_ONLY &&
|
|
mTransaction->mHasBeenActiveOnConnectionThread) {
|
|
Database* database = mTransaction->GetDatabase();
|
|
MOZ_ASSERT(database);
|
|
|
|
if (DatabaseConnection* connection = database->GetConnection()) {
|
|
// May be null if the VersionChangeOp was canceled.
|
|
DatabaseConnection::UpdateRefcountFunction* fileRefcountFunction =
|
|
connection->GetUpdateRefcountFunction();
|
|
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
if (fileRefcountFunction) {
|
|
mResultCode = fileRefcountFunction->WillCommit();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode),
|
|
"WillCommit() failed!");
|
|
}
|
|
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = WriteAutoIncrementCounts();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode),
|
|
"WriteAutoIncrementCounts() failed!");
|
|
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
AssertForeignKeyConsistency(connection);
|
|
|
|
mResultCode = connection->CommitWriteTransaction();
|
|
NS_WARNING_ASSERTION(NS_SUCCEEDED(mResultCode), "Commit failed!");
|
|
|
|
if (NS_SUCCEEDED(mResultCode) &&
|
|
mTransaction->GetMode() == IDBTransaction::READ_WRITE_FLUSH) {
|
|
mResultCode = connection->Checkpoint();
|
|
}
|
|
|
|
if (NS_SUCCEEDED(mResultCode) && fileRefcountFunction) {
|
|
fileRefcountFunction->DidCommit();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(mResultCode)) {
|
|
if (fileRefcountFunction) {
|
|
fileRefcountFunction->DidAbort();
|
|
}
|
|
|
|
connection->RollbackWriteTransaction();
|
|
}
|
|
|
|
CommitOrRollbackAutoIncrementCounts();
|
|
|
|
connection->FinishWriteTransaction();
|
|
|
|
if (mTransaction->GetMode() == IDBTransaction::CLEANUP) {
|
|
connection->DoIdleProcessing(/* aNeedsCheckpoint */ true);
|
|
|
|
connection->EnableQuotaChecks();
|
|
}
|
|
}
|
|
}
|
|
|
|
IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld] Request[%llu]: "
|
|
"Finished database work",
|
|
"IndexedDB %s: P T[%lld] R[%llu]: DB End",
|
|
IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
|
|
mTransaction->LoggingSerialNumber(),
|
|
mLoggingSerialNumber);
|
|
|
|
IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld]: "
|
|
"Finished database work",
|
|
"IndexedDB %s: P T[%lld]: DB End",
|
|
IDB_LOG_ID_STRING(mBackgroundChildLoggingId),
|
|
mLoggingSerialNumber);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
TransactionBase::
|
|
CommitOp::TransactionFinishedBeforeUnblock()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mTransaction);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CommitOp::TransactionFinishedBeforeUnblock",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (!IsActorDestroyed()) {
|
|
mTransaction->UpdateMetadata(mResultCode);
|
|
}
|
|
}
|
|
|
|
void
|
|
TransactionBase::
|
|
CommitOp::TransactionFinishedAfterUnblock()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mTransaction);
|
|
|
|
IDB_LOG_MARK("IndexedDB %s: Parent Transaction[%lld]: "
|
|
"Finished with result 0x%x",
|
|
"IndexedDB %s: P T[%lld]: Transaction finished (0x%x)",
|
|
IDB_LOG_ID_STRING(mTransaction->GetLoggingInfo()->Id()),
|
|
mTransaction->LoggingSerialNumber(),
|
|
mResultCode);
|
|
|
|
mTransaction->SendCompleteNotification(ClampResultCode(mResultCode));
|
|
|
|
Database* database = mTransaction->GetDatabase();
|
|
MOZ_ASSERT(database);
|
|
|
|
database->UnregisterTransaction(mTransaction);
|
|
|
|
mTransaction = nullptr;
|
|
|
|
#ifdef DEBUG
|
|
// A bit hacky but the CommitOp is not really a normal database operation
|
|
// that is tied to an actor. Do this to make our assertions happy.
|
|
NoteActorDestroyed();
|
|
#endif
|
|
}
|
|
|
|
DatabaseOp::DatabaseOp(Database* aDatabase)
|
|
: DatabaseOperationBase(aDatabase->GetLoggingInfo()->Id(),
|
|
aDatabase->GetLoggingInfo()->NextRequestSN())
|
|
, mDatabase(aDatabase)
|
|
, mState(State::Initial)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aDatabase);
|
|
}
|
|
|
|
nsresult
|
|
DatabaseOp::SendToIOThread()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::Initial);
|
|
|
|
if (!OperationMayProceed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
if (NS_WARN_IF(!quotaManager)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
// Must set this before dispatching otherwise we will race with the IO thread.
|
|
mState = State::DatabaseWork;
|
|
|
|
nsresult rv = quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DatabaseOp::Run()
|
|
{
|
|
nsresult rv;
|
|
|
|
switch (mState) {
|
|
case State::Initial:
|
|
rv = SendToIOThread();
|
|
break;
|
|
|
|
case State::DatabaseWork:
|
|
rv = DoDatabaseWork();
|
|
break;
|
|
|
|
case State::SendingResults:
|
|
SendResults();
|
|
return NS_OK;
|
|
|
|
default:
|
|
MOZ_CRASH("Bad state!");
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv)) && mState != State::SendingResults) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = rv;
|
|
}
|
|
|
|
// Must set mState before dispatching otherwise we will race with the owning
|
|
// thread.
|
|
mState = State::SendingResults;
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DatabaseOp::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
NoteActorDestroyed();
|
|
}
|
|
|
|
CreateFileOp::CreateFileOp(Database* aDatabase,
|
|
const DatabaseRequestParams& aParams)
|
|
: DatabaseOp(aDatabase)
|
|
, mParams(aParams.get_CreateFileParams())
|
|
{
|
|
MOZ_ASSERT(aParams.type() == DatabaseRequestParams::TCreateFileParams);
|
|
}
|
|
|
|
nsresult
|
|
CreateFileOp::CreateMutableFile(MutableFile** aMutableFile)
|
|
{
|
|
nsCOMPtr<nsIFile> file = GetFileForFileInfo(mFileInfo);
|
|
if (NS_WARN_IF(!file)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
RefPtr<MutableFile> mutableFile =
|
|
MutableFile::Create(file, mDatabase, mFileInfo);
|
|
if (NS_WARN_IF(!mutableFile)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
// Transfer ownership to IPDL.
|
|
mutableFile->SetActorAlive();
|
|
|
|
if (!mDatabase->SendPBackgroundMutableFileConstructor(mutableFile,
|
|
mParams.name(),
|
|
mParams.type())) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
mutableFile.forget(aMutableFile);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CreateFileOp::DoDatabaseWork()
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(mState == State::DatabaseWork);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CreateFileOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
|
|
NS_WARNING("Refusing to create file because disk space is low!");
|
|
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
|
|
}
|
|
|
|
if (NS_WARN_IF(QuotaManager::IsShuttingDown()) || !OperationMayProceed()) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
FileManager* fileManager = mDatabase->GetFileManager();
|
|
|
|
mFileInfo = fileManager->GetNewFileInfo();
|
|
if (NS_WARN_IF(!mFileInfo)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
const int64_t fileId = mFileInfo->Id();
|
|
|
|
nsCOMPtr<nsIFile> journalDirectory = fileManager->EnsureJournalDirectory();
|
|
if (NS_WARN_IF(!journalDirectory)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> journalFile =
|
|
fileManager->GetFileForId(journalDirectory, fileId);
|
|
if (NS_WARN_IF(!journalFile)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
nsresult rv = journalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> fileDirectory = fileManager->GetDirectory();
|
|
if (NS_WARN_IF(!fileDirectory)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> file = fileManager->GetFileForId(fileDirectory, fileId);
|
|
if (NS_WARN_IF(!file)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Must set mState before dispatching otherwise we will race with the owning
|
|
// thread.
|
|
mState = State::SendingResults;
|
|
|
|
rv = mOwningThread->Dispatch(this, NS_DISPATCH_NORMAL);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
CreateFileOp::SendResults()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mState == State::SendingResults);
|
|
|
|
if (!IsActorDestroyed() && !mDatabase->IsInvalidated()) {
|
|
DatabaseRequestResponse response;
|
|
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
RefPtr<MutableFile> mutableFile;
|
|
nsresult rv = CreateMutableFile(getter_AddRefs(mutableFile));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
// We successfully created a mutable file so use its actor as the
|
|
// success result for this request.
|
|
CreateFileRequestResponse createResponse;
|
|
createResponse.mutableFileParent() = mutableFile;
|
|
response = createResponse;
|
|
} else {
|
|
response = ClampResultCode(rv);
|
|
#ifdef DEBUG
|
|
mResultCode = response.get_nsresult();
|
|
#endif
|
|
}
|
|
} else {
|
|
response = ClampResultCode(mResultCode);
|
|
}
|
|
|
|
Unused <<
|
|
PBackgroundIDBDatabaseRequestParent::Send__delete__(this, response);
|
|
}
|
|
|
|
mState = State::Completed;
|
|
}
|
|
|
|
nsresult
|
|
VersionChangeTransactionOp::SendSuccessResult()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
// Nothing to send here, the API assumes that this request always succeeds.
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransactionOp::SendFailureResult(nsresult aResultCode)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
// The only option here is to cause the transaction to abort.
|
|
return false;
|
|
}
|
|
|
|
void
|
|
VersionChangeTransactionOp::Cleanup()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
#ifdef DEBUG
|
|
// A bit hacky but the VersionChangeTransactionOp is not generated in response
|
|
// to a child request like most other database operations. Do this to make our
|
|
// assertions happy.
|
|
NoteActorDestroyed();
|
|
#endif
|
|
|
|
TransactionDatabaseOperationBase::Cleanup();
|
|
}
|
|
|
|
nsresult
|
|
CreateObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CreateObjectStoreOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
|
|
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
// Make sure that we're not creating an object store with the same name as
|
|
// another that already exists. This should be impossible because we should
|
|
// have thrown an error long before now...
|
|
DatabaseConnection::CachedStatement stmt;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"SELECT name "
|
|
"FROM object_store "
|
|
"WHERE name = :name;"),
|
|
&stmt));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mMetadata.name()));
|
|
|
|
bool hasResult;
|
|
MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
|
|
MOZ_ASSERT(!hasResult);
|
|
}
|
|
#endif
|
|
|
|
DatabaseConnection::AutoSavepoint autoSave;
|
|
nsresult rv = autoSave.Start(Transaction());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_store (id, auto_increment, name, key_path) "
|
|
"VALUES (:id, :auto_increment, :name, :key_path);"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mMetadata.id());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("auto_increment"),
|
|
mMetadata.autoIncrement() ? 1 : 0);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mMetadata.name());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
NS_NAMED_LITERAL_CSTRING(keyPath, "key_path");
|
|
|
|
if (mMetadata.keyPath().IsValid()) {
|
|
nsAutoString keyPathSerialization;
|
|
mMetadata.keyPath().SerializeToString(keyPathSerialization);
|
|
|
|
rv = stmt->BindStringByName(keyPath, keyPathSerialization);
|
|
} else {
|
|
rv = stmt->BindNullByName(keyPath);
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
int64_t id;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aConnection->GetStorageConnection()->GetLastInsertRowID(&id));
|
|
MOZ_ASSERT(mMetadata.id() == id);
|
|
}
|
|
#endif
|
|
|
|
rv = autoSave.Commit();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DeleteObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DeleteObjectStoreOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
// Make sure |mIsLastObjectStore| is telling the truth.
|
|
DatabaseConnection::CachedStatement stmt;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"SELECT id "
|
|
"FROM object_store;"),
|
|
&stmt));
|
|
|
|
bool foundThisObjectStore = false;
|
|
bool foundOtherObjectStore = false;
|
|
|
|
while (true) {
|
|
bool hasResult;
|
|
MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
|
|
|
|
if (!hasResult) {
|
|
break;
|
|
}
|
|
|
|
int64_t id;
|
|
MOZ_ALWAYS_SUCCEEDS(stmt->GetInt64(0, &id));
|
|
|
|
if (id == mMetadata->mCommonMetadata.id()) {
|
|
foundThisObjectStore = true;
|
|
} else {
|
|
foundOtherObjectStore = true;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT_IF(mIsLastObjectStore,
|
|
foundThisObjectStore && !foundOtherObjectStore);
|
|
MOZ_ASSERT_IF(!mIsLastObjectStore,
|
|
foundThisObjectStore && foundOtherObjectStore);
|
|
}
|
|
#endif
|
|
|
|
DatabaseConnection::AutoSavepoint autoSave;
|
|
nsresult rv = autoSave.Start(Transaction());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (mIsLastObjectStore) {
|
|
// We can just delete everything if this is the last object store.
|
|
DatabaseConnection::CachedStatement stmt;
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM index_data;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM unique_index_data;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM object_data;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM object_store_index;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM object_store;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
bool hasIndexes;
|
|
rv = ObjectStoreHasIndexes(aConnection,
|
|
mMetadata->mCommonMetadata.id(),
|
|
&hasIndexes);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (hasIndexes) {
|
|
rv = DeleteObjectStoreDataTableRowsWithIndexes(
|
|
aConnection,
|
|
mMetadata->mCommonMetadata.id(),
|
|
void_t());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Now clean up the object store index table.
|
|
DatabaseConnection::CachedStatement stmt;
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM object_store_index "
|
|
"WHERE object_store_id = :object_store_id;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(objectStoreIdString,
|
|
mMetadata->mCommonMetadata.id());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
// We only have to worry about object data if this object store has no
|
|
// indexes.
|
|
DatabaseConnection::CachedStatement stmt;
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM object_data "
|
|
"WHERE object_store_id = :object_store_id;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(objectStoreIdString,
|
|
mMetadata->mCommonMetadata.id());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM object_store "
|
|
"WHERE id = :object_store_id;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(objectStoreIdString,
|
|
mMetadata->mCommonMetadata.id());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
int32_t deletedRowCount;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aConnection->GetStorageConnection()->
|
|
GetAffectedRows(&deletedRowCount));
|
|
MOZ_ASSERT(deletedRowCount == 1);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
rv = autoSave.Commit();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (mMetadata->mCommonMetadata.autoIncrement()) {
|
|
Transaction()->ForgetModifiedAutoIncrementObjectStore(mMetadata);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
RenameObjectStoreOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"RenameObjectStoreOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
|
|
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
// Make sure that we're not renaming an object store with the same name as
|
|
// another that already exists. This should be impossible because we should
|
|
// have thrown an error long before now...
|
|
DatabaseConnection::CachedStatement stmt;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"SELECT name "
|
|
"FROM object_store "
|
|
"WHERE name = :name "
|
|
"AND id != :id;"),
|
|
&stmt));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mNewName));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mId));
|
|
|
|
bool hasResult;
|
|
MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
|
|
MOZ_ASSERT(!hasResult);
|
|
}
|
|
#endif
|
|
|
|
DatabaseConnection::AutoSavepoint autoSave;
|
|
nsresult rv = autoSave.Start(Transaction());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"UPDATE object_store "
|
|
"SET name = :name "
|
|
"WHERE id = :id;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mNewName);
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mId);
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = autoSave.Commit();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
CreateIndexOp::CreateIndexOp(VersionChangeTransaction* aTransaction,
|
|
const int64_t aObjectStoreId,
|
|
const IndexMetadata& aMetadata)
|
|
: VersionChangeTransactionOp(aTransaction)
|
|
, mMetadata(aMetadata)
|
|
, mFileManager(aTransaction->GetDatabase()->GetFileManager())
|
|
, mDatabaseId(aTransaction->DatabaseId())
|
|
, mObjectStoreId(aObjectStoreId)
|
|
{
|
|
MOZ_ASSERT(aObjectStoreId);
|
|
MOZ_ASSERT(aMetadata.id());
|
|
MOZ_ASSERT(mFileManager);
|
|
MOZ_ASSERT(!mDatabaseId.IsEmpty());
|
|
}
|
|
|
|
unsigned int CreateIndexOp::sThreadLocalIndex = kBadThreadLocalIndex;
|
|
|
|
nsresult
|
|
CreateIndexOp::InsertDataFromObjectStore(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(!IndexedDatabaseManager::InLowDiskSpaceMode());
|
|
MOZ_ASSERT(mMaybeUniqueIndexTable);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CreateIndexOp::InsertDataFromObjectStore",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsCOMPtr<mozIStorageConnection> storageConnection =
|
|
aConnection->GetStorageConnection();
|
|
MOZ_ASSERT(storageConnection);
|
|
|
|
ThreadLocalJSContext* context = ThreadLocalJSContext::GetOrCreate();
|
|
if (NS_WARN_IF(!context)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
JSContext* cx = context->Context();
|
|
JSAutoRequest ar(cx);
|
|
JSAutoCompartment ac(cx, context->Global());
|
|
|
|
RefPtr<UpdateIndexDataValuesFunction> updateFunction =
|
|
new UpdateIndexDataValuesFunction(this, aConnection, cx);
|
|
|
|
NS_NAMED_LITERAL_CSTRING(updateFunctionName, "update_index_data_values");
|
|
|
|
nsresult rv =
|
|
storageConnection->CreateFunction(updateFunctionName,
|
|
4,
|
|
updateFunction);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = InsertDataFromObjectStoreInternal(aConnection);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(storageConnection->RemoveFunction(updateFunctionName));
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CreateIndexOp::InsertDataFromObjectStoreInternal(
|
|
DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(!IndexedDatabaseManager::InLowDiskSpaceMode());
|
|
MOZ_ASSERT(mMaybeUniqueIndexTable);
|
|
|
|
DebugOnly<void*> storageConnection = aConnection->GetStorageConnection();
|
|
MOZ_ASSERT(storageConnection);
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"UPDATE object_data "
|
|
"SET index_data_values = update_index_data_values "
|
|
"(key, index_data_values, file_ids, data) "
|
|
"WHERE object_store_id = :object_store_id;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
|
|
mObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
CreateIndexOp::Init(TransactionBase* aTransaction)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aTransaction);
|
|
|
|
struct MOZ_STACK_CLASS Helper final
|
|
{
|
|
static void
|
|
Destroy(void* aThreadLocal)
|
|
{
|
|
delete static_cast<ThreadLocalJSContext*>(aThreadLocal);
|
|
}
|
|
};
|
|
|
|
if (sThreadLocalIndex == kBadThreadLocalIndex) {
|
|
if (NS_WARN_IF(PR_SUCCESS !=
|
|
PR_NewThreadPrivateIndex(&sThreadLocalIndex,
|
|
&Helper::Destroy))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(sThreadLocalIndex != kBadThreadLocalIndex);
|
|
|
|
nsresult rv =
|
|
GetUniqueIndexTableForObjectStore(aTransaction,
|
|
mObjectStoreId,
|
|
mMaybeUniqueIndexTable);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
nsresult
|
|
CreateIndexOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CreateIndexOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
|
|
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
// Make sure that we're not creating an index with the same name and object
|
|
// store as another that already exists. This should be impossible because
|
|
// we should have thrown an error long before now...
|
|
DatabaseConnection::CachedStatement stmt;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"SELECT name "
|
|
"FROM object_store_index "
|
|
"WHERE object_store_id = :osid "
|
|
"AND name = :name;"),
|
|
&stmt));
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStoreId));
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mMetadata.name()));
|
|
|
|
bool hasResult;
|
|
MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
|
|
|
|
MOZ_ASSERT(!hasResult);
|
|
}
|
|
#endif
|
|
|
|
DatabaseConnection::AutoSavepoint autoSave;
|
|
nsresult rv = autoSave.Start(Transaction());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_store_index (id, name, key_path, unique_index, "
|
|
"multientry, object_store_id, locale, "
|
|
"is_auto_locale) "
|
|
"VALUES (:id, :name, :key_path, :unique, :multientry, :osid, :locale, "
|
|
":is_auto_locale)"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mMetadata.id());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mMetadata.name());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsAutoString keyPathSerialization;
|
|
mMetadata.keyPath().SerializeToString(keyPathSerialization);
|
|
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key_path"),
|
|
keyPathSerialization);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("unique"),
|
|
mMetadata.unique() ? 1 : 0);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("multientry"),
|
|
mMetadata.multiEntry() ? 1 : 0);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (mMetadata.locale().IsEmpty()) {
|
|
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("locale"));
|
|
} else {
|
|
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("locale"),
|
|
mMetadata.locale());
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("is_auto_locale"),
|
|
mMetadata.autoLocale());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
int64_t id;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aConnection->GetStorageConnection()->GetLastInsertRowID(&id));
|
|
MOZ_ASSERT(mMetadata.id() == id);
|
|
}
|
|
#endif
|
|
|
|
rv = InsertDataFromObjectStore(aConnection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = autoSave.Commit();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
static const JSClassOps sNormalJSContextGlobalClassOps = {
|
|
/* addProperty */ nullptr,
|
|
/* delProperty */ nullptr,
|
|
/* getProperty */ nullptr,
|
|
/* setProperty */ nullptr,
|
|
/* enumerate */ nullptr,
|
|
/* resolve */ nullptr,
|
|
/* mayResolve */ nullptr,
|
|
/* finalize */ nullptr,
|
|
/* call */ nullptr,
|
|
/* hasInstance */ nullptr,
|
|
/* construct */ nullptr,
|
|
/* trace */ JS_GlobalObjectTraceHook
|
|
};
|
|
|
|
const JSClass NormalJSContext::sGlobalClass = {
|
|
"IndexedDBTransactionThreadGlobal",
|
|
JSCLASS_GLOBAL_FLAGS,
|
|
&sNormalJSContextGlobalClassOps
|
|
};
|
|
|
|
bool
|
|
NormalJSContext::Init()
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
mContext = JS_NewContext(kContextHeapSize);
|
|
if (NS_WARN_IF(!mContext)) {
|
|
return false;
|
|
}
|
|
|
|
// Let everyone know that we might be able to call JS. This alerts the
|
|
// profiler about certain possible deadlocks.
|
|
NS_GetCurrentThread()->SetCanInvokeJS(true);
|
|
|
|
// Not setting this will cause JS_CHECK_RECURSION to report false positives.
|
|
JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024);
|
|
|
|
if (NS_WARN_IF(!JS::InitSelfHostedCode(mContext))) {
|
|
return false;
|
|
}
|
|
|
|
JSAutoRequest ar(mContext);
|
|
|
|
JS::CompartmentOptions options;
|
|
mGlobal = JS_NewGlobalObject(mContext, &sGlobalClass, nullptr,
|
|
JS::FireOnNewGlobalHook, options);
|
|
if (NS_WARN_IF(!mGlobal)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// static
|
|
NormalJSContext*
|
|
NormalJSContext::Create()
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
|
|
nsAutoPtr<NormalJSContext> newContext(new NormalJSContext());
|
|
|
|
if (NS_WARN_IF(!newContext->Init())) {
|
|
return nullptr;
|
|
}
|
|
|
|
return newContext.forget();
|
|
}
|
|
|
|
// static
|
|
auto
|
|
CreateIndexOp::
|
|
ThreadLocalJSContext::GetOrCreate() -> ThreadLocalJSContext*
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(CreateIndexOp::kBadThreadLocalIndex !=
|
|
CreateIndexOp::sThreadLocalIndex);
|
|
|
|
auto* context = static_cast<ThreadLocalJSContext*>(
|
|
PR_GetThreadPrivate(CreateIndexOp::sThreadLocalIndex));
|
|
if (context) {
|
|
return context;
|
|
}
|
|
|
|
nsAutoPtr<ThreadLocalJSContext> newContext(new ThreadLocalJSContext());
|
|
|
|
if (NS_WARN_IF(!newContext->Init())) {
|
|
return nullptr;
|
|
}
|
|
|
|
DebugOnly<PRStatus> status =
|
|
PR_SetThreadPrivate(CreateIndexOp::sThreadLocalIndex, newContext);
|
|
MOZ_ASSERT(status == PR_SUCCESS);
|
|
|
|
return newContext.forget();
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(CreateIndexOp::UpdateIndexDataValuesFunction,
|
|
mozIStorageFunction);
|
|
|
|
NS_IMETHODIMP
|
|
CreateIndexOp::
|
|
UpdateIndexDataValuesFunction::OnFunctionCall(mozIStorageValueArray* aValues,
|
|
nsIVariant** _retval)
|
|
{
|
|
MOZ_ASSERT(aValues);
|
|
MOZ_ASSERT(_retval);
|
|
MOZ_ASSERT(mConnection);
|
|
mConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mOp);
|
|
MOZ_ASSERT(mCx);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CreateIndexOp::UpdateIndexDataValuesFunction::OnFunctionCall",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
uint32_t argCount;
|
|
MOZ_ALWAYS_SUCCEEDS(aValues->GetNumEntries(&argCount));
|
|
MOZ_ASSERT(argCount == 4); // key, index_data_values, file_ids, data
|
|
|
|
int32_t valueType;
|
|
MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType));
|
|
MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(1, &valueType));
|
|
MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
|
|
valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(2, &valueType));
|
|
MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
|
|
valueType == mozIStorageValueArray::VALUE_TYPE_TEXT);
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(3, &valueType));
|
|
MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB ||
|
|
valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);
|
|
}
|
|
#endif
|
|
|
|
StructuredCloneReadInfo cloneInfo;
|
|
nsresult rv =
|
|
GetStructuredCloneReadInfoFromValueArray(aValues,
|
|
/* aDataIndex */ 3,
|
|
/* aFileIdsIndex */ 2,
|
|
mOp->mFileManager,
|
|
&cloneInfo);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
JS::Rooted<JS::Value> clone(mCx);
|
|
if (NS_WARN_IF(!IDBObjectStore::DeserializeIndexValue(mCx,
|
|
cloneInfo,
|
|
&clone))) {
|
|
return NS_ERROR_DOM_DATA_CLONE_ERR;
|
|
}
|
|
|
|
const IndexMetadata& metadata = mOp->mMetadata;
|
|
const int64_t& objectStoreId = mOp->mObjectStoreId;
|
|
|
|
AutoTArray<IndexUpdateInfo, 32> updateInfos;
|
|
rv = IDBObjectStore::AppendIndexUpdateInfo(metadata.id(),
|
|
metadata.keyPath(),
|
|
metadata.unique(),
|
|
metadata.multiEntry(),
|
|
metadata.locale(),
|
|
mCx,
|
|
clone,
|
|
updateInfos);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (updateInfos.IsEmpty()) {
|
|
// XXX See if we can do this without copying...
|
|
|
|
nsCOMPtr<nsIVariant> unmodifiedValue;
|
|
|
|
// No changes needed, just return the original value.
|
|
int32_t valueType;
|
|
rv = aValues->GetTypeOfIndex(1, &valueType);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
|
|
valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
|
|
|
|
if (valueType == mozIStorageValueArray::VALUE_TYPE_NULL) {
|
|
unmodifiedValue = new storage::NullVariant();
|
|
unmodifiedValue.forget(_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
|
|
|
|
const uint8_t* blobData;
|
|
uint32_t blobDataLength;
|
|
rv = aValues->GetSharedBlob(1, &blobDataLength, &blobData);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
std::pair<uint8_t *, int> copiedBlobDataPair(
|
|
static_cast<uint8_t*>(malloc(blobDataLength)),
|
|
blobDataLength);
|
|
|
|
if (!copiedBlobDataPair.first) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
memcpy(copiedBlobDataPair.first, blobData, blobDataLength);
|
|
|
|
unmodifiedValue = new storage::AdoptedBlobVariant(copiedBlobDataPair);
|
|
unmodifiedValue.forget(_retval);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
Key key;
|
|
rv = key.SetFromValueArray(aValues, 0);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
AutoTArray<IndexDataValue, 32> indexValues;
|
|
rv = ReadCompressedIndexDataValues(aValues, 1, indexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
const bool hadPreviousIndexValues = !indexValues.IsEmpty();
|
|
|
|
const uint32_t updateInfoCount = updateInfos.Length();
|
|
|
|
if (NS_WARN_IF(!indexValues.SetCapacity(indexValues.Length() +
|
|
updateInfoCount, fallible))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// First construct the full list to update the index_data_values row.
|
|
for (uint32_t index = 0; index < updateInfoCount; index++) {
|
|
const IndexUpdateInfo& info = updateInfos[index];
|
|
|
|
MOZ_ALWAYS_TRUE(
|
|
indexValues.InsertElementSorted(IndexDataValue(metadata.id(),
|
|
metadata.unique(),
|
|
info.value(),
|
|
info.localizedValue()),
|
|
fallible));
|
|
}
|
|
|
|
UniqueFreePtr<uint8_t> indexValuesBlob;
|
|
uint32_t indexValuesBlobLength;
|
|
rv = MakeCompressedIndexDataValues(indexValues,
|
|
indexValuesBlob,
|
|
&indexValuesBlobLength);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(!indexValuesBlobLength == !(indexValuesBlob.get()));
|
|
|
|
nsCOMPtr<nsIVariant> value;
|
|
|
|
if (!indexValuesBlob) {
|
|
value = new storage::NullVariant();
|
|
|
|
value.forget(_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Now insert the new table rows. We only need to construct a new list if
|
|
// the full list is different.
|
|
if (hadPreviousIndexValues) {
|
|
indexValues.ClearAndRetainStorage();
|
|
|
|
MOZ_ASSERT(indexValues.Capacity() >= updateInfoCount);
|
|
|
|
for (uint32_t index = 0; index < updateInfoCount; index++) {
|
|
const IndexUpdateInfo& info = updateInfos[index];
|
|
|
|
MOZ_ALWAYS_TRUE(
|
|
indexValues.InsertElementSorted(IndexDataValue(metadata.id(),
|
|
metadata.unique(),
|
|
info.value(),
|
|
info.localizedValue()),
|
|
fallible));
|
|
}
|
|
}
|
|
|
|
rv = InsertIndexTableRows(mConnection, objectStoreId, key, indexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
std::pair<uint8_t *, int> copiedBlobDataPair(indexValuesBlob.release(),
|
|
indexValuesBlobLength);
|
|
|
|
value = new storage::AdoptedBlobVariant(copiedBlobDataPair);
|
|
|
|
value.forget(_retval);
|
|
return NS_OK;
|
|
}
|
|
|
|
DeleteIndexOp::DeleteIndexOp(VersionChangeTransaction* aTransaction,
|
|
const int64_t aObjectStoreId,
|
|
const int64_t aIndexId,
|
|
const bool aUnique,
|
|
const bool aIsLastIndex)
|
|
: VersionChangeTransactionOp(aTransaction)
|
|
, mObjectStoreId(aObjectStoreId)
|
|
, mIndexId(aIndexId)
|
|
, mUnique(aUnique)
|
|
, mIsLastIndex(aIsLastIndex)
|
|
{
|
|
MOZ_ASSERT(aObjectStoreId);
|
|
MOZ_ASSERT(aIndexId);
|
|
}
|
|
|
|
nsresult
|
|
DeleteIndexOp::RemoveReferencesToIndex(DatabaseConnection* aConnection,
|
|
const Key& aObjectStoreKey,
|
|
nsTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aConnection);
|
|
MOZ_ASSERT(!aObjectStoreKey.IsUnset());
|
|
MOZ_ASSERT_IF(!mIsLastIndex, !aIndexValues.IsEmpty());
|
|
|
|
struct MOZ_STACK_CLASS IndexIdComparator final
|
|
{
|
|
bool
|
|
Equals(const IndexDataValue& aA, const IndexDataValue& aB) const
|
|
{
|
|
// Ignore everything but the index id.
|
|
return aA.mIndexId == aB.mIndexId;
|
|
};
|
|
|
|
bool
|
|
LessThan(const IndexDataValue& aA, const IndexDataValue& aB) const
|
|
{
|
|
return aA.mIndexId < aB.mIndexId;
|
|
};
|
|
};
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DeleteIndexOp::RemoveReferencesToIndex",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (mIsLastIndex) {
|
|
// There is no need to parse the previous entry in the index_data_values
|
|
// column if this is the last index. Simply set it to NULL.
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"UPDATE object_data "
|
|
"SET index_data_values = NULL "
|
|
"WHERE object_store_id = :object_store_id "
|
|
"AND key = :key;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
|
|
mObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aObjectStoreKey.BindToStatement(stmt, NS_LITERAL_CSTRING("key"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
IndexDataValue search;
|
|
search.mIndexId = mIndexId;
|
|
|
|
// This returns the first element that matches our index id found during a
|
|
// binary search. However, there could still be other elements before that.
|
|
size_t firstElementIndex =
|
|
aIndexValues.BinaryIndexOf(search, IndexIdComparator());
|
|
if (NS_WARN_IF(firstElementIndex == aIndexValues.NoIndex) ||
|
|
NS_WARN_IF(aIndexValues[firstElementIndex].mIndexId != mIndexId)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
MOZ_ASSERT(aIndexValues[firstElementIndex].mIndexId == mIndexId);
|
|
|
|
// Walk backwards to find the real first index.
|
|
while (firstElementIndex) {
|
|
if (aIndexValues[firstElementIndex - 1].mIndexId == mIndexId) {
|
|
firstElementIndex--;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(aIndexValues[firstElementIndex].mIndexId == mIndexId);
|
|
|
|
const size_t indexValuesLength = aIndexValues.Length();
|
|
|
|
// Find the last element with the same index id.
|
|
size_t lastElementIndex = firstElementIndex;
|
|
|
|
while (lastElementIndex < indexValuesLength) {
|
|
if (aIndexValues[lastElementIndex].mIndexId == mIndexId) {
|
|
lastElementIndex++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(lastElementIndex > firstElementIndex);
|
|
MOZ_ASSERT_IF(lastElementIndex < indexValuesLength,
|
|
aIndexValues[lastElementIndex].mIndexId != mIndexId);
|
|
MOZ_ASSERT(aIndexValues[lastElementIndex - 1].mIndexId == mIndexId);
|
|
|
|
aIndexValues.RemoveElementsAt(firstElementIndex,
|
|
lastElementIndex - firstElementIndex);
|
|
|
|
nsresult rv = UpdateIndexValues(aConnection,
|
|
mObjectStoreId,
|
|
aObjectStoreKey,
|
|
aIndexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DeleteIndexOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
// Make sure |mIsLastIndex| is telling the truth.
|
|
DatabaseConnection::CachedStatement stmt;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"SELECT id "
|
|
"FROM object_store_index "
|
|
"WHERE object_store_id = :object_store_id;"),
|
|
&stmt));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
|
|
mObjectStoreId));
|
|
|
|
bool foundThisIndex = false;
|
|
bool foundOtherIndex = false;
|
|
|
|
while (true) {
|
|
bool hasResult;
|
|
MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
|
|
|
|
if (!hasResult) {
|
|
break;
|
|
}
|
|
|
|
int64_t id;
|
|
MOZ_ALWAYS_SUCCEEDS(stmt->GetInt64(0, &id));
|
|
|
|
if (id == mIndexId) {
|
|
foundThisIndex = true;
|
|
} else {
|
|
foundOtherIndex = true;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT_IF(mIsLastIndex, foundThisIndex && !foundOtherIndex);
|
|
MOZ_ASSERT_IF(!mIsLastIndex, foundThisIndex && foundOtherIndex);
|
|
}
|
|
#endif
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"DeleteIndexOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
DatabaseConnection::AutoSavepoint autoSave;
|
|
nsresult rv = autoSave.Start(Transaction());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
DatabaseConnection::CachedStatement selectStmt;
|
|
|
|
// mozStorage warns that these statements trigger a sort operation but we
|
|
// don't care because this is a very rare call and we expect it to be slow.
|
|
// The cost of having an index on this field is too high.
|
|
if (mUnique) {
|
|
if (mIsLastIndex) {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"/* do not warn (bug someone else) */ "
|
|
"SELECT value, object_data_key "
|
|
"FROM unique_index_data "
|
|
"WHERE index_id = :index_id "
|
|
"ORDER BY object_data_key ASC;"),
|
|
&selectStmt);
|
|
} else {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"/* do not warn (bug out) */ "
|
|
"SELECT unique_index_data.value, "
|
|
"unique_index_data.object_data_key, "
|
|
"object_data.index_data_values "
|
|
"FROM unique_index_data "
|
|
"JOIN object_data "
|
|
"ON unique_index_data.object_data_key = object_data.key "
|
|
"WHERE unique_index_data.index_id = :index_id "
|
|
"AND object_data.object_store_id = :object_store_id "
|
|
"ORDER BY unique_index_data.object_data_key ASC;"),
|
|
&selectStmt);
|
|
}
|
|
} else {
|
|
if (mIsLastIndex) {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"/* do not warn (bug me not) */ "
|
|
"SELECT value, object_data_key "
|
|
"FROM index_data "
|
|
"WHERE index_id = :index_id "
|
|
"AND object_store_id = :object_store_id "
|
|
"ORDER BY object_data_key ASC;"),
|
|
&selectStmt);
|
|
} else {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"/* do not warn (bug off) */ "
|
|
"SELECT index_data.value, "
|
|
"index_data.object_data_key, "
|
|
"object_data.index_data_values "
|
|
"FROM index_data "
|
|
"JOIN object_data "
|
|
"ON index_data.object_data_key = object_data.key "
|
|
"WHERE index_data.index_id = :index_id "
|
|
"AND object_data.object_store_id = :object_store_id "
|
|
"ORDER BY index_data.object_data_key ASC;"),
|
|
&selectStmt);
|
|
}
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
NS_NAMED_LITERAL_CSTRING(indexIdString, "index_id");
|
|
|
|
rv = selectStmt->BindInt64ByName(indexIdString, mIndexId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!mUnique || !mIsLastIndex) {
|
|
rv = selectStmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
|
|
mObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
NS_NAMED_LITERAL_CSTRING(valueString, "value");
|
|
NS_NAMED_LITERAL_CSTRING(objectDataKeyString, "object_data_key");
|
|
|
|
DatabaseConnection::CachedStatement deleteIndexRowStmt;
|
|
DatabaseConnection::CachedStatement nullIndexDataValuesStmt;
|
|
|
|
Key lastObjectStoreKey;
|
|
AutoTArray<IndexDataValue, 32> lastIndexValues;
|
|
|
|
bool hasResult;
|
|
while (NS_SUCCEEDED(rv = selectStmt->ExecuteStep(&hasResult)) && hasResult) {
|
|
// We always need the index key to delete the index row.
|
|
Key indexKey;
|
|
rv = indexKey.SetFromStatement(selectStmt, 0);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(indexKey.IsUnset())) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
// Don't call |lastObjectStoreKey.BindToStatement()| directly because we
|
|
// don't want to copy the same key multiple times.
|
|
const uint8_t* objectStoreKeyData;
|
|
uint32_t objectStoreKeyDataLength;
|
|
rv = selectStmt->GetSharedBlob(1,
|
|
&objectStoreKeyDataLength,
|
|
&objectStoreKeyData);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!objectStoreKeyDataLength)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
nsDependentCString currentObjectStoreKeyBuffer(
|
|
reinterpret_cast<const char*>(objectStoreKeyData),
|
|
objectStoreKeyDataLength);
|
|
if (currentObjectStoreKeyBuffer != lastObjectStoreKey.GetBuffer()) {
|
|
// We just walked to the next object store key.
|
|
if (!lastObjectStoreKey.IsUnset()) {
|
|
// Before we move on to the next key we need to update the previous
|
|
// key's index_data_values column.
|
|
rv = RemoveReferencesToIndex(aConnection,
|
|
lastObjectStoreKey,
|
|
lastIndexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Save the object store key.
|
|
lastObjectStoreKey = Key(currentObjectStoreKeyBuffer);
|
|
|
|
// And the |index_data_values| row if this isn't the only index.
|
|
if (!mIsLastIndex) {
|
|
lastIndexValues.ClearAndRetainStorage();
|
|
rv = ReadCompressedIndexDataValues(selectStmt, 2, lastIndexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(lastIndexValues.IsEmpty())) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now delete the index row.
|
|
if (deleteIndexRowStmt) {
|
|
MOZ_ALWAYS_SUCCEEDS(deleteIndexRowStmt->Reset());
|
|
} else {
|
|
if (mUnique) {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM unique_index_data "
|
|
"WHERE index_id = :index_id "
|
|
"AND value = :value;"),
|
|
&deleteIndexRowStmt);
|
|
} else {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM index_data "
|
|
"WHERE index_id = :index_id "
|
|
"AND value = :value "
|
|
"AND object_data_key = :object_data_key;"),
|
|
&deleteIndexRowStmt);
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = deleteIndexRowStmt->BindInt64ByName(indexIdString, mIndexId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = indexKey.BindToStatement(deleteIndexRowStmt, valueString);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!mUnique) {
|
|
rv = lastObjectStoreKey.BindToStatement(deleteIndexRowStmt,
|
|
objectDataKeyString);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = deleteIndexRowStmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Take care of the last key.
|
|
if (!lastObjectStoreKey.IsUnset()) {
|
|
MOZ_ASSERT_IF(!mIsLastIndex, !lastIndexValues.IsEmpty());
|
|
|
|
rv = RemoveReferencesToIndex(aConnection,
|
|
lastObjectStoreKey,
|
|
lastIndexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
DatabaseConnection::CachedStatement deleteStmt;
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM object_store_index "
|
|
"WHERE id = :index_id;"),
|
|
&deleteStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = deleteStmt->BindInt64ByName(indexIdString, mIndexId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = deleteStmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
int32_t deletedRowCount;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aConnection->GetStorageConnection()->GetAffectedRows(&deletedRowCount));
|
|
MOZ_ASSERT(deletedRowCount == 1);
|
|
}
|
|
#endif
|
|
|
|
rv = autoSave.Commit();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
RenameIndexOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"RenameIndexOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
|
|
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
// Make sure that we're not renaming an index with the same name as another
|
|
// that already exists. This should be impossible because we should have
|
|
// thrown an error long before now...
|
|
DatabaseConnection::CachedStatement stmt;
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"SELECT name "
|
|
"FROM object_store_index "
|
|
"WHERE object_store_id = :object_store_id "
|
|
"AND name = :name "
|
|
"AND id != :id;"),
|
|
&stmt));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
|
|
mObjectStoreId));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mNewName));
|
|
|
|
MOZ_ALWAYS_SUCCEEDS(
|
|
stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mIndexId));
|
|
|
|
bool hasResult;
|
|
MOZ_ALWAYS_SUCCEEDS(stmt->ExecuteStep(&hasResult));
|
|
MOZ_ASSERT(!hasResult);
|
|
}
|
|
#else
|
|
Unused << mObjectStoreId;
|
|
#endif
|
|
|
|
DatabaseConnection::AutoSavepoint autoSave;
|
|
nsresult rv = autoSave.Start(Transaction());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"UPDATE object_store_index "
|
|
"SET name = :name "
|
|
"WHERE id = :id;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), mNewName);
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), mIndexId);
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = autoSave.Commit();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
NormalTransactionOp::ObjectStoreHasIndexes(NormalTransactionOp* aOp,
|
|
DatabaseConnection* aConnection,
|
|
const int64_t aObjectStoreId,
|
|
const bool aMayHaveIndexes,
|
|
bool* aHasIndexes)
|
|
{
|
|
MOZ_ASSERT(aOp);
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aObjectStoreId);
|
|
MOZ_ASSERT(aHasIndexes);
|
|
|
|
bool hasIndexes;
|
|
if (aOp->Transaction()->GetMode() == IDBTransaction::VERSION_CHANGE &&
|
|
aMayHaveIndexes) {
|
|
// If this is a version change transaction then mObjectStoreMayHaveIndexes
|
|
// could be wrong (e.g. if a unique index failed to be created due to a
|
|
// constraint error). We have to check on this thread by asking the database
|
|
// directly.
|
|
nsresult rv =
|
|
DatabaseOperationBase::ObjectStoreHasIndexes(aConnection,
|
|
aObjectStoreId,
|
|
&hasIndexes);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
MOZ_ASSERT(NS_SUCCEEDED(
|
|
DatabaseOperationBase::ObjectStoreHasIndexes(aConnection,
|
|
aObjectStoreId,
|
|
&hasIndexes)));
|
|
MOZ_ASSERT(aMayHaveIndexes == hasIndexes);
|
|
|
|
hasIndexes = aMayHaveIndexes;
|
|
}
|
|
|
|
*aHasIndexes = hasIndexes;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
NormalTransactionOp::GetPreprocessParams(PreprocessParams& aParams)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
NormalTransactionOp::SendPreprocessInfo()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
PreprocessParams params;
|
|
nsresult rv = GetPreprocessParams(params);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(params.type() != PreprocessParams::T__None);
|
|
|
|
if (NS_WARN_IF(!PBackgroundIDBRequestParent::SendPreprocess(params))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
NormalTransactionOp::SendSuccessResult()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
if (!IsActorDestroyed()) {
|
|
RequestResponse response;
|
|
GetResponse(response);
|
|
|
|
MOZ_ASSERT(response.type() != RequestResponse::T__None);
|
|
|
|
if (response.type() == RequestResponse::Tnsresult) {
|
|
MOZ_ASSERT(NS_FAILED(response.get_nsresult()));
|
|
|
|
return response.get_nsresult();
|
|
}
|
|
|
|
if (NS_WARN_IF(!PBackgroundIDBRequestParent::Send__delete__(this,
|
|
response))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
mResponseSent = true;
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
NormalTransactionOp::SendFailureResult(nsresult aResultCode)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(NS_FAILED(aResultCode));
|
|
|
|
bool result = false;
|
|
|
|
if (!IsActorDestroyed()) {
|
|
result =
|
|
PBackgroundIDBRequestParent::Send__delete__(this,
|
|
ClampResultCode(aResultCode));
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
mResponseSent = true;
|
|
#endif
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
NormalTransactionOp::Cleanup()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT_IF(!IsActorDestroyed(), mResponseSent);
|
|
|
|
TransactionDatabaseOperationBase::Cleanup();
|
|
}
|
|
|
|
void
|
|
NormalTransactionOp::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
NoteActorDestroyed();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
NormalTransactionOp::RecvContinue(const PreprocessResponse& aResponse)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
switch (aResponse.type()) {
|
|
case PreprocessResponse::Tnsresult:
|
|
mResultCode = aResponse.get_nsresult();
|
|
break;
|
|
|
|
case PreprocessResponse::TObjectStoreGetPreprocessResponse:
|
|
break;
|
|
|
|
case PreprocessResponse::TObjectStoreGetAllPreprocessResponse:
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
NoteContinueReceived();
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
ObjectStoreAddOrPutRequestOp::ObjectStoreAddOrPutRequestOp(
|
|
TransactionBase* aTransaction,
|
|
const RequestParams& aParams)
|
|
: NormalTransactionOp(aTransaction)
|
|
, mParams(aParams.type() == RequestParams::TObjectStoreAddParams ?
|
|
aParams.get_ObjectStoreAddParams().commonParams() :
|
|
aParams.get_ObjectStorePutParams().commonParams())
|
|
, mGroup(aTransaction->GetDatabase()->Group())
|
|
, mOrigin(aTransaction->GetDatabase()->Origin())
|
|
, mPersistenceType(aTransaction->GetDatabase()->Type())
|
|
, mOverwrite(aParams.type() == RequestParams::TObjectStorePutParams)
|
|
, mObjectStoreMayHaveIndexes(false)
|
|
{
|
|
MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreAddParams ||
|
|
aParams.type() == RequestParams::TObjectStorePutParams);
|
|
|
|
mMetadata =
|
|
aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId());
|
|
MOZ_ASSERT(mMetadata);
|
|
|
|
mObjectStoreMayHaveIndexes = mMetadata->HasLiveIndexes();
|
|
|
|
mDataOverThreshold =
|
|
snappy::MaxCompressedLength(mParams.cloneInfo().data().data.Size()) >
|
|
IndexedDatabaseManager::DataThreshold();
|
|
}
|
|
|
|
nsresult
|
|
ObjectStoreAddOrPutRequestOp::RemoveOldIndexDataValues(
|
|
DatabaseConnection* aConnection)
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aConnection);
|
|
MOZ_ASSERT(mOverwrite);
|
|
MOZ_ASSERT(!mResponse.IsUnset());
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
bool hasIndexes = false;
|
|
MOZ_ASSERT(NS_SUCCEEDED(
|
|
DatabaseOperationBase::ObjectStoreHasIndexes(aConnection,
|
|
mParams.objectStoreId(),
|
|
&hasIndexes)));
|
|
MOZ_ASSERT(hasIndexes,
|
|
"Don't use this slow method if there are no indexes!");
|
|
}
|
|
#endif
|
|
|
|
DatabaseConnection::CachedStatement indexValuesStmt;
|
|
nsresult rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"SELECT index_data_values "
|
|
"FROM object_data "
|
|
"WHERE object_store_id = :object_store_id "
|
|
"AND key = :key;"),
|
|
&indexValuesStmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = indexValuesStmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
|
|
mParams.objectStoreId());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = mResponse.BindToStatement(indexValuesStmt, NS_LITERAL_CSTRING("key"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool hasResult;
|
|
rv = indexValuesStmt->ExecuteStep(&hasResult);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (hasResult) {
|
|
AutoTArray<IndexDataValue, 32> existingIndexValues;
|
|
rv = ReadCompressedIndexDataValues(indexValuesStmt,
|
|
0,
|
|
existingIndexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = DeleteIndexDataTableRows(aConnection, mResponse, existingIndexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
ObjectStoreAddOrPutRequestOp::Init(TransactionBase* aTransaction)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
const nsTArray<IndexUpdateInfo>& indexUpdateInfos =
|
|
mParams.indexUpdateInfos();
|
|
|
|
if (!indexUpdateInfos.IsEmpty()) {
|
|
const uint32_t count = indexUpdateInfos.Length();
|
|
|
|
mUniqueIndexTable.emplace();
|
|
|
|
for (uint32_t index = 0; index < count; index++) {
|
|
const IndexUpdateInfo& updateInfo = indexUpdateInfos[index];
|
|
|
|
RefPtr<FullIndexMetadata> indexMetadata;
|
|
MOZ_ALWAYS_TRUE(mMetadata->mIndexes.Get(updateInfo.indexId(),
|
|
getter_AddRefs(indexMetadata)));
|
|
|
|
MOZ_ASSERT(!indexMetadata->mDeleted);
|
|
|
|
const int64_t& indexId = indexMetadata->mCommonMetadata.id();
|
|
const bool& unique = indexMetadata->mCommonMetadata.unique();
|
|
|
|
MOZ_ASSERT(indexId == updateInfo.indexId());
|
|
MOZ_ASSERT_IF(!indexMetadata->mCommonMetadata.multiEntry(),
|
|
!mUniqueIndexTable.ref().Get(indexId));
|
|
|
|
if (NS_WARN_IF(!mUniqueIndexTable.ref().Put(indexId, unique, fallible))) {
|
|
return false;
|
|
}
|
|
}
|
|
} else if (mOverwrite) {
|
|
mUniqueIndexTable.emplace();
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
if (mUniqueIndexTable.isSome()) {
|
|
mUniqueIndexTable.ref().MarkImmutable();
|
|
}
|
|
#endif
|
|
|
|
const nsTArray<FileAddInfo>& fileAddInfos = mParams.fileAddInfos();
|
|
|
|
if (!fileAddInfos.IsEmpty()) {
|
|
const uint32_t count = fileAddInfos.Length();
|
|
|
|
if (NS_WARN_IF(!mStoredFileInfos.SetCapacity(count, fallible))) {
|
|
return false;
|
|
}
|
|
|
|
for (uint32_t index = 0; index < count; index++) {
|
|
const FileAddInfo& fileAddInfo = fileAddInfos[index];
|
|
|
|
MOZ_ASSERT(fileAddInfo.type() == StructuredCloneFile::eBlob ||
|
|
fileAddInfo.type() == StructuredCloneFile::eMutableFile ||
|
|
fileAddInfo.type() == StructuredCloneFile::eWasmBytecode ||
|
|
fileAddInfo.type() == StructuredCloneFile::eWasmCompiled);
|
|
|
|
const DatabaseOrMutableFile& file = fileAddInfo.file();
|
|
|
|
StoredFileInfo* storedFileInfo = mStoredFileInfos.AppendElement(fallible);
|
|
MOZ_ASSERT(storedFileInfo);
|
|
|
|
switch (fileAddInfo.type()) {
|
|
case StructuredCloneFile::eBlob: {
|
|
MOZ_ASSERT(file.type() ==
|
|
DatabaseOrMutableFile::TPBackgroundIDBDatabaseFileParent);
|
|
|
|
storedFileInfo->mFileActor =
|
|
static_cast<DatabaseFile*>(
|
|
file.get_PBackgroundIDBDatabaseFileParent());
|
|
MOZ_ASSERT(storedFileInfo->mFileActor);
|
|
|
|
storedFileInfo->mFileInfo = storedFileInfo->mFileActor->GetFileInfo();
|
|
MOZ_ASSERT(storedFileInfo->mFileInfo);
|
|
|
|
storedFileInfo->mType = StructuredCloneFile::eBlob;
|
|
break;
|
|
}
|
|
|
|
case StructuredCloneFile::eMutableFile: {
|
|
MOZ_ASSERT(file.type() ==
|
|
DatabaseOrMutableFile::TPBackgroundMutableFileParent);
|
|
|
|
auto mutableFileActor =
|
|
static_cast<MutableFile*>(
|
|
file.get_PBackgroundMutableFileParent());
|
|
MOZ_ASSERT(mutableFileActor);
|
|
|
|
storedFileInfo->mFileInfo = mutableFileActor->GetFileInfo();
|
|
MOZ_ASSERT(storedFileInfo->mFileInfo);
|
|
|
|
storedFileInfo->mType = StructuredCloneFile::eMutableFile;
|
|
break;
|
|
}
|
|
|
|
case StructuredCloneFile::eWasmBytecode:
|
|
case StructuredCloneFile::eWasmCompiled: {
|
|
MOZ_ASSERT(file.type() ==
|
|
DatabaseOrMutableFile::TPBackgroundIDBDatabaseFileParent);
|
|
|
|
storedFileInfo->mFileActor =
|
|
static_cast<DatabaseFile*>(
|
|
file.get_PBackgroundIDBDatabaseFileParent());
|
|
MOZ_ASSERT(storedFileInfo->mFileActor);
|
|
|
|
storedFileInfo->mFileInfo = storedFileInfo->mFileActor->GetFileInfo();
|
|
MOZ_ASSERT(storedFileInfo->mFileInfo);
|
|
|
|
storedFileInfo->mType = fileAddInfo.type();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mDataOverThreshold) {
|
|
StoredFileInfo* storedFileInfo = mStoredFileInfos.AppendElement(fallible);
|
|
MOZ_ASSERT(storedFileInfo);
|
|
|
|
RefPtr<FileManager> fileManager =
|
|
aTransaction->GetDatabase()->GetFileManager();
|
|
MOZ_ASSERT(fileManager);
|
|
|
|
storedFileInfo->mFileInfo = fileManager->GetNewFileInfo();
|
|
|
|
storedFileInfo->mInputStream =
|
|
new SCInputStream(mParams.cloneInfo().data().data);
|
|
|
|
storedFileInfo->mType = StructuredCloneFile::eStructuredClone;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
nsresult
|
|
ObjectStoreAddOrPutRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aConnection->GetStorageConnection());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ObjectStoreAddOrPutRequestOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (NS_WARN_IF(IndexedDatabaseManager::InLowDiskSpaceMode())) {
|
|
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
|
|
}
|
|
|
|
DatabaseConnection::AutoSavepoint autoSave;
|
|
nsresult rv = autoSave.Start(Transaction());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool objectStoreHasIndexes;
|
|
rv = ObjectStoreHasIndexes(this,
|
|
aConnection,
|
|
mParams.objectStoreId(),
|
|
mObjectStoreMayHaveIndexes,
|
|
&objectStoreHasIndexes);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// This will be the final key we use.
|
|
Key& key = mResponse;
|
|
key = mParams.key();
|
|
|
|
const bool keyUnset = key.IsUnset();
|
|
const int64_t osid = mParams.objectStoreId();
|
|
|
|
// First delete old index_data_values if we're overwriting something and we
|
|
// have indexes.
|
|
if (mOverwrite && !keyUnset && objectStoreHasIndexes) {
|
|
rv = RemoveOldIndexDataValues(aConnection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// The "|| keyUnset" here is mostly a debugging tool. If a key isn't
|
|
// specified we should never have a collision and so it shouldn't matter
|
|
// if we allow overwrite or not. By not allowing overwrite we raise
|
|
// detectable errors rather than corrupting data.
|
|
DatabaseConnection::CachedStatement stmt;
|
|
if (!mOverwrite || keyUnset) {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_data "
|
|
"(object_store_id, key, file_ids, data) "
|
|
"VALUES (:osid, :key, :file_ids, :data);"),
|
|
&stmt);
|
|
} else {
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"INSERT OR REPLACE INTO object_data "
|
|
"(object_store_id, key, file_ids, data) "
|
|
"VALUES (:osid, :key, :file_ids, :data);"),
|
|
&stmt);
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), osid);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
const SerializedStructuredCloneWriteInfo& cloneInfo = mParams.cloneInfo();
|
|
const JSStructuredCloneData& cloneData = cloneInfo.data().data;
|
|
size_t cloneDataSize = cloneData.Size();
|
|
|
|
MOZ_ASSERT(!keyUnset || mMetadata->mCommonMetadata.autoIncrement(),
|
|
"Should have key unless autoIncrement");
|
|
|
|
int64_t autoIncrementNum = 0;
|
|
|
|
if (mMetadata->mCommonMetadata.autoIncrement()) {
|
|
if (keyUnset) {
|
|
autoIncrementNum = mMetadata->mNextAutoIncrementId;
|
|
|
|
MOZ_ASSERT(autoIncrementNum > 0);
|
|
|
|
if (autoIncrementNum > (1LL << 53)) {
|
|
return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
|
|
}
|
|
|
|
key.SetFromInteger(autoIncrementNum);
|
|
} else if (key.IsFloat() &&
|
|
key.ToFloat() >= mMetadata->mNextAutoIncrementId) {
|
|
autoIncrementNum = floor(key.ToFloat());
|
|
}
|
|
|
|
if (keyUnset && mMetadata->mCommonMetadata.keyPath().IsValid()) {
|
|
const SerializedStructuredCloneWriteInfo& cloneInfo = mParams.cloneInfo();
|
|
MOZ_ASSERT(cloneInfo.offsetToKeyProp());
|
|
MOZ_ASSERT(cloneDataSize > sizeof(uint64_t));
|
|
MOZ_ASSERT(cloneInfo.offsetToKeyProp() <=
|
|
(cloneDataSize - sizeof(uint64_t)));
|
|
|
|
// Special case where someone put an object into an autoIncrement'ing
|
|
// objectStore with no key in its keyPath set. We needed to figure out
|
|
// which row id we would get above before we could set that properly.
|
|
uint64_t keyPropValue =
|
|
ReinterpretDoubleAsUInt64(static_cast<double>(autoIncrementNum));
|
|
|
|
static const size_t keyPropSize = sizeof(uint64_t);
|
|
|
|
char keyPropBuffer[keyPropSize];
|
|
LittleEndian::writeUint64(keyPropBuffer, keyPropValue);
|
|
|
|
auto iter = cloneData.Iter();
|
|
DebugOnly<bool> result =
|
|
iter.AdvanceAcrossSegments(cloneData, cloneInfo.offsetToKeyProp());
|
|
MOZ_ASSERT(result);
|
|
|
|
for (char index : keyPropBuffer) {
|
|
char* keyPropPointer = iter.Data();
|
|
*keyPropPointer = index;
|
|
|
|
result = iter.AdvanceAcrossSegments(cloneData, 1);
|
|
MOZ_ASSERT(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
key.BindToStatement(stmt, NS_LITERAL_CSTRING("key"));
|
|
|
|
if (mDataOverThreshold) {
|
|
// The data we store in the SQLite database is a (signed) 64-bit integer.
|
|
// The flags are left-shifted 32 bits so the max value is 0xFFFFFFFF.
|
|
// The file_ids index occupies the lower 32 bits and its max is 0xFFFFFFFF.
|
|
static const uint32_t kCompressedFlag = (1<<0);
|
|
|
|
uint32_t flags = 0;
|
|
flags |= kCompressedFlag;
|
|
|
|
uint32_t index = mStoredFileInfos.Length() - 1;
|
|
|
|
int64_t data = (uint64_t(flags) << 32) | index;
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("data"), data);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
nsCString flatCloneData;
|
|
flatCloneData.SetLength(cloneDataSize);
|
|
auto iter = cloneData.Iter();
|
|
cloneData.ReadBytes(iter, flatCloneData.BeginWriting(), cloneDataSize);
|
|
|
|
// Compress the bytes before adding into the database.
|
|
const char* uncompressed = flatCloneData.BeginReading();
|
|
size_t uncompressedLength = cloneDataSize;
|
|
|
|
size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
|
|
|
|
UniqueFreePtr<char> compressed(
|
|
static_cast<char*>(malloc(compressedLength)));
|
|
if (NS_WARN_IF(!compressed)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
snappy::RawCompress(uncompressed, uncompressedLength, compressed.get(),
|
|
&compressedLength);
|
|
|
|
uint8_t* dataBuffer = reinterpret_cast<uint8_t*>(compressed.release());
|
|
size_t dataBufferLength = compressedLength;
|
|
|
|
rv = stmt->BindAdoptedBlobByName(NS_LITERAL_CSTRING("data"), dataBuffer,
|
|
dataBufferLength);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (!mStoredFileInfos.IsEmpty()) {
|
|
// Moved outside the loop to allow it to be cached when demanded by the
|
|
// first write. (We may have mStoredFileInfos without any required writes.)
|
|
Maybe<FileHelper> fileHelper;
|
|
nsAutoString fileIds;
|
|
|
|
for (uint32_t count = mStoredFileInfos.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
StoredFileInfo& storedFileInfo = mStoredFileInfos[index];
|
|
MOZ_ASSERT(storedFileInfo.mFileInfo);
|
|
|
|
// If there is a StoredFileInfo, then one of the following is true:
|
|
// - This was an overflow structured clone and storedFileInfo.mInputStream
|
|
// MUST be non-null.
|
|
// - This is a reference to a Blob that may or may not have already been
|
|
// written to disk. storedFileInfo.mFileActor MUST be non-null, but
|
|
// its GetBlockingInputStream may return null (so don't assert on them).
|
|
// - It's a mutable file. No writing will be performed.
|
|
MOZ_ASSERT(storedFileInfo.mInputStream || storedFileInfo.mFileActor ||
|
|
storedFileInfo.mType == StructuredCloneFile::eMutableFile);
|
|
|
|
nsCOMPtr<nsIInputStream> inputStream;
|
|
// Check for an explicit stream, like a structured clone stream.
|
|
storedFileInfo.mInputStream.swap(inputStream);
|
|
// Check for a blob-backed stream otherwise.
|
|
if (!inputStream && storedFileInfo.mFileActor) {
|
|
ErrorResult streamRv;
|
|
inputStream =
|
|
storedFileInfo.mFileActor->GetBlockingInputStream(streamRv);
|
|
if (NS_WARN_IF(streamRv.Failed())) {
|
|
return streamRv.StealNSResult();
|
|
}
|
|
}
|
|
|
|
if (inputStream) {
|
|
if (fileHelper.isNothing()) {
|
|
RefPtr<FileManager> fileManager =
|
|
Transaction()->GetDatabase()->GetFileManager();
|
|
MOZ_ASSERT(fileManager);
|
|
|
|
fileHelper.emplace(fileManager);
|
|
rv = fileHelper->Init();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
}
|
|
|
|
RefPtr<FileInfo>& fileInfo = storedFileInfo.mFileInfo;
|
|
|
|
nsCOMPtr<nsIFile> file = fileHelper->GetFile(fileInfo);
|
|
if (NS_WARN_IF(!file)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> journalFile =
|
|
fileHelper->GetJournalFile(fileInfo);
|
|
if (NS_WARN_IF(!journalFile)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
bool compress =
|
|
storedFileInfo.mType == StructuredCloneFile::eStructuredClone;
|
|
|
|
rv = fileHelper->CreateFileFromStream(file,
|
|
journalFile,
|
|
inputStream,
|
|
compress);
|
|
if (NS_FAILED(rv) &&
|
|
NS_ERROR_GET_MODULE(rv) != NS_ERROR_MODULE_DOM_INDEXEDDB) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
// Try to remove the file if the copy failed.
|
|
nsresult rv2 = fileHelper->RemoveFile(file, journalFile);
|
|
if (NS_WARN_IF(NS_FAILED(rv2))) {
|
|
return rv;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
if (storedFileInfo.mFileActor) {
|
|
storedFileInfo.mFileActor->WriteSucceededClearBlobImpl();
|
|
}
|
|
}
|
|
|
|
if (index) {
|
|
fileIds.Append(' ');
|
|
}
|
|
storedFileInfo.Serialize(fileIds);
|
|
}
|
|
|
|
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("file_ids"), fileIds);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
rv = stmt->BindNullByName(NS_LITERAL_CSTRING("file_ids"));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (rv == NS_ERROR_STORAGE_CONSTRAINT) {
|
|
MOZ_ASSERT(!keyUnset, "Generated key had a collision!");
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Update our indexes if needed.
|
|
if (!mParams.indexUpdateInfos().IsEmpty()) {
|
|
MOZ_ASSERT(mUniqueIndexTable.isSome());
|
|
|
|
// Write the index_data_values column.
|
|
AutoTArray<IndexDataValue, 32> indexValues;
|
|
rv = IndexDataValuesFromUpdateInfos(mParams.indexUpdateInfos(),
|
|
mUniqueIndexTable.ref(),
|
|
indexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = UpdateIndexValues(aConnection, osid, key, indexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = InsertIndexTableRows(aConnection, osid, key, indexValues);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = autoSave.Commit();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (autoIncrementNum) {
|
|
mMetadata->mNextAutoIncrementId = autoIncrementNum + 1;
|
|
Transaction()->NoteModifiedAutoIncrementObjectStore(mMetadata);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
ObjectStoreAddOrPutRequestOp::GetResponse(RequestResponse& aResponse)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
if (mOverwrite) {
|
|
aResponse = ObjectStorePutResponse(mResponse);
|
|
} else {
|
|
aResponse = ObjectStoreAddResponse(mResponse);
|
|
}
|
|
}
|
|
|
|
void
|
|
ObjectStoreAddOrPutRequestOp::Cleanup()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
|
|
mStoredFileInfos.Clear();
|
|
|
|
NormalTransactionOp::Cleanup();
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(ObjectStoreAddOrPutRequestOp::SCInputStream, nsIInputStream)
|
|
|
|
NS_IMETHODIMP
|
|
ObjectStoreAddOrPutRequestOp::
|
|
SCInputStream::Close()
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ObjectStoreAddOrPutRequestOp::
|
|
SCInputStream::Available(uint64_t* _retval)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ObjectStoreAddOrPutRequestOp::
|
|
SCInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval)
|
|
{
|
|
return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ObjectStoreAddOrPutRequestOp::
|
|
SCInputStream::ReadSegments(nsWriteSegmentFun aWriter,
|
|
void* aClosure,
|
|
uint32_t aCount,
|
|
uint32_t* _retval)
|
|
{
|
|
*_retval = 0;
|
|
|
|
while (aCount) {
|
|
uint32_t count = std::min(uint32_t(mIter.RemainingInSegment()), aCount);
|
|
if (!count) {
|
|
// We've run out of data in the last segment.
|
|
break;
|
|
}
|
|
|
|
uint32_t written;
|
|
nsresult rv =
|
|
aWriter(this, aClosure, mIter.Data(), *_retval, count, &written);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
// InputStreams do not propagate errors to caller.
|
|
return NS_OK;
|
|
}
|
|
|
|
// Writer should write what we asked it to write.
|
|
MOZ_ASSERT(written == count);
|
|
|
|
*_retval += count;
|
|
aCount -= count;
|
|
|
|
mIter.Advance(mData, count);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
ObjectStoreAddOrPutRequestOp::
|
|
SCInputStream::IsNonBlocking(bool* _retval)
|
|
{
|
|
*_retval = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
ObjectStoreGetRequestOp::ObjectStoreGetRequestOp(TransactionBase* aTransaction,
|
|
const RequestParams& aParams,
|
|
bool aGetAll)
|
|
: NormalTransactionOp(aTransaction)
|
|
, mObjectStoreId(aGetAll ?
|
|
aParams.get_ObjectStoreGetAllParams().objectStoreId() :
|
|
aParams.get_ObjectStoreGetParams().objectStoreId())
|
|
, mDatabase(aTransaction->GetDatabase())
|
|
, mOptionalKeyRange(aGetAll ?
|
|
aParams.get_ObjectStoreGetAllParams()
|
|
.optionalKeyRange() :
|
|
OptionalKeyRange(aParams.get_ObjectStoreGetParams()
|
|
.keyRange()))
|
|
, mBackgroundParent(aTransaction->GetBackgroundParent())
|
|
, mPreprocessInfoCount(0)
|
|
, mLimit(aGetAll ? aParams.get_ObjectStoreGetAllParams().limit() : 1)
|
|
, mGetAll(aGetAll)
|
|
{
|
|
MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreGetParams ||
|
|
aParams.type() == RequestParams::TObjectStoreGetAllParams);
|
|
MOZ_ASSERT(mObjectStoreId);
|
|
MOZ_ASSERT(mDatabase);
|
|
MOZ_ASSERT_IF(!aGetAll,
|
|
mOptionalKeyRange.type() ==
|
|
OptionalKeyRange::TSerializedKeyRange);
|
|
MOZ_ASSERT(mBackgroundParent);
|
|
}
|
|
|
|
template <typename T>
|
|
void MoveData(StructuredCloneReadInfo& aInfo, T& aResult);
|
|
|
|
template <>
|
|
void
|
|
MoveData<SerializedStructuredCloneReadInfo>(
|
|
StructuredCloneReadInfo& aInfo,
|
|
SerializedStructuredCloneReadInfo& aResult)
|
|
{
|
|
aResult.data().data = Move(aInfo.mData);
|
|
aResult.hasPreprocessInfo() = aInfo.mHasPreprocessInfo;
|
|
}
|
|
|
|
template <>
|
|
void
|
|
MoveData<WasmModulePreprocessInfo>(StructuredCloneReadInfo& aInfo,
|
|
WasmModulePreprocessInfo& aResult)
|
|
{
|
|
}
|
|
|
|
template <bool aForPreprocess, typename T>
|
|
nsresult
|
|
ObjectStoreGetRequestOp::ConvertResponse(StructuredCloneReadInfo& aInfo,
|
|
T& aResult)
|
|
{
|
|
MoveData(aInfo, aResult);
|
|
|
|
FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
|
|
nsresult rv = SerializeStructuredCloneFiles(mBackgroundParent,
|
|
mDatabase,
|
|
aInfo.mFiles,
|
|
aForPreprocess,
|
|
serializedFiles);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(aResult.files().IsEmpty());
|
|
|
|
aResult.files().SwapElements(serializedFiles);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
ObjectStoreGetRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT_IF(!mGetAll,
|
|
mOptionalKeyRange.type() ==
|
|
OptionalKeyRange::TSerializedKeyRange);
|
|
MOZ_ASSERT_IF(!mGetAll, mLimit == 1);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ObjectStoreGetRequestOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const bool hasKeyRange =
|
|
mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
|
|
|
|
nsAutoCString keyRangeClause;
|
|
if (hasKeyRange) {
|
|
GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
NS_LITERAL_CSTRING("key"),
|
|
keyRangeClause);
|
|
}
|
|
|
|
nsCString limitClause;
|
|
if (mLimit) {
|
|
limitClause.AssignLiteral(" LIMIT ");
|
|
limitClause.AppendInt(mLimit);
|
|
}
|
|
|
|
nsCString query =
|
|
NS_LITERAL_CSTRING("SELECT file_ids, data "
|
|
"FROM object_data "
|
|
"WHERE object_store_id = :osid") +
|
|
keyRangeClause +
|
|
NS_LITERAL_CSTRING(" ORDER BY key ASC") +
|
|
limitClause;
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(query, &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (hasKeyRange) {
|
|
rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
bool hasResult;
|
|
while (NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
|
|
StructuredCloneReadInfo* cloneInfo = mResponse.AppendElement(fallible);
|
|
if (NS_WARN_IF(!cloneInfo)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
rv = GetStructuredCloneReadInfoFromStatement(stmt, 1, 0,
|
|
mDatabase->GetFileManager(),
|
|
cloneInfo);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (cloneInfo->mHasPreprocessInfo) {
|
|
mPreprocessInfoCount++;
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
ObjectStoreGetRequestOp::HasPreprocessInfo()
|
|
{
|
|
return mPreprocessInfoCount > 0;
|
|
}
|
|
|
|
nsresult
|
|
ObjectStoreGetRequestOp::GetPreprocessParams(PreprocessParams& aParams)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(!mResponse.IsEmpty());
|
|
|
|
if (mGetAll) {
|
|
aParams = ObjectStoreGetAllPreprocessParams();
|
|
|
|
FallibleTArray<WasmModulePreprocessInfo> falliblePreprocessInfos;
|
|
if (NS_WARN_IF(!falliblePreprocessInfos.SetLength(mPreprocessInfoCount,
|
|
fallible))) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
uint32_t fallibleIndex = 0;
|
|
for (uint32_t count = mResponse.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
StructuredCloneReadInfo& info = mResponse[index];
|
|
|
|
if (info.mHasPreprocessInfo) {
|
|
nsresult rv =
|
|
ConvertResponse<true>(info, falliblePreprocessInfos[fallibleIndex++]);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
|
|
nsTArray<WasmModulePreprocessInfo>& preprocessInfos =
|
|
aParams.get_ObjectStoreGetAllPreprocessParams().preprocessInfos();
|
|
|
|
falliblePreprocessInfos.SwapElements(preprocessInfos);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
aParams = ObjectStoreGetPreprocessParams();
|
|
|
|
WasmModulePreprocessInfo& preprocessInfo =
|
|
aParams.get_ObjectStoreGetPreprocessParams().preprocessInfo();
|
|
|
|
nsresult rv = ConvertResponse<true>(mResponse[0], preprocessInfo);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
ObjectStoreGetRequestOp::GetResponse(RequestResponse& aResponse)
|
|
{
|
|
MOZ_ASSERT_IF(mLimit, mResponse.Length() <= mLimit);
|
|
|
|
if (mGetAll) {
|
|
aResponse = ObjectStoreGetAllResponse();
|
|
|
|
if (!mResponse.IsEmpty()) {
|
|
FallibleTArray<SerializedStructuredCloneReadInfo> fallibleCloneInfos;
|
|
if (NS_WARN_IF(!fallibleCloneInfos.SetLength(mResponse.Length(),
|
|
fallible))) {
|
|
aResponse = NS_ERROR_OUT_OF_MEMORY;
|
|
return;
|
|
}
|
|
|
|
for (uint32_t count = mResponse.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
nsresult rv =
|
|
ConvertResponse<false>(mResponse[index], fallibleCloneInfos[index]);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
aResponse = rv;
|
|
return;
|
|
}
|
|
}
|
|
|
|
nsTArray<SerializedStructuredCloneReadInfo>& cloneInfos =
|
|
aResponse.get_ObjectStoreGetAllResponse().cloneInfos();
|
|
|
|
fallibleCloneInfos.SwapElements(cloneInfos);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
aResponse = ObjectStoreGetResponse();
|
|
|
|
if (!mResponse.IsEmpty()) {
|
|
SerializedStructuredCloneReadInfo& serializedInfo =
|
|
aResponse.get_ObjectStoreGetResponse().cloneInfo();
|
|
|
|
nsresult rv = ConvertResponse<false>(mResponse[0], serializedInfo);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
aResponse = rv;
|
|
}
|
|
}
|
|
}
|
|
|
|
ObjectStoreGetKeyRequestOp::ObjectStoreGetKeyRequestOp(
|
|
TransactionBase* aTransaction,
|
|
const RequestParams& aParams,
|
|
bool aGetAll)
|
|
: NormalTransactionOp(aTransaction)
|
|
, mObjectStoreId(aGetAll ?
|
|
aParams.get_ObjectStoreGetAllKeysParams().objectStoreId() :
|
|
aParams.get_ObjectStoreGetKeyParams().objectStoreId())
|
|
, mOptionalKeyRange(aGetAll ?
|
|
aParams.get_ObjectStoreGetAllKeysParams()
|
|
.optionalKeyRange() :
|
|
OptionalKeyRange(aParams.get_ObjectStoreGetKeyParams()
|
|
.keyRange()))
|
|
, mLimit(aGetAll ? aParams.get_ObjectStoreGetAllKeysParams().limit() : 1)
|
|
, mGetAll(aGetAll)
|
|
{
|
|
MOZ_ASSERT(aParams.type() == RequestParams::TObjectStoreGetKeyParams ||
|
|
aParams.type() == RequestParams::TObjectStoreGetAllKeysParams);
|
|
MOZ_ASSERT(mObjectStoreId);
|
|
MOZ_ASSERT_IF(!aGetAll,
|
|
mOptionalKeyRange.type() ==
|
|
OptionalKeyRange::TSerializedKeyRange);
|
|
}
|
|
|
|
nsresult
|
|
ObjectStoreGetKeyRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ObjectStoreGetKeyRequestOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const bool hasKeyRange =
|
|
mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
|
|
|
|
nsAutoCString keyRangeClause;
|
|
if (hasKeyRange) {
|
|
GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
NS_LITERAL_CSTRING("key"),
|
|
keyRangeClause);
|
|
}
|
|
|
|
nsAutoCString limitClause;
|
|
if (mLimit) {
|
|
limitClause.AssignLiteral(" LIMIT ");
|
|
limitClause.AppendInt(mLimit);
|
|
}
|
|
|
|
nsCString query =
|
|
NS_LITERAL_CSTRING("SELECT key "
|
|
"FROM object_data "
|
|
"WHERE object_store_id = :osid") +
|
|
keyRangeClause +
|
|
NS_LITERAL_CSTRING(" ORDER BY key ASC") +
|
|
limitClause;
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(query, &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"), mObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (hasKeyRange) {
|
|
rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
bool hasResult;
|
|
while(NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
|
|
Key* key = mResponse.AppendElement(fallible);
|
|
if (NS_WARN_IF(!key)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
rv = key->SetFromStatement(stmt, 0);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
ObjectStoreGetKeyRequestOp::GetResponse(RequestResponse& aResponse)
|
|
{
|
|
MOZ_ASSERT_IF(mLimit, mResponse.Length() <= mLimit);
|
|
|
|
if (mGetAll) {
|
|
aResponse = ObjectStoreGetAllKeysResponse();
|
|
|
|
if (!mResponse.IsEmpty()) {
|
|
nsTArray<Key>& response =
|
|
aResponse.get_ObjectStoreGetAllKeysResponse().keys();
|
|
mResponse.SwapElements(response);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
aResponse = ObjectStoreGetKeyResponse();
|
|
|
|
if (!mResponse.IsEmpty()) {
|
|
aResponse.get_ObjectStoreGetKeyResponse().key() = Move(mResponse[0]);
|
|
}
|
|
}
|
|
|
|
ObjectStoreDeleteRequestOp::ObjectStoreDeleteRequestOp(
|
|
TransactionBase* aTransaction,
|
|
const ObjectStoreDeleteParams& aParams)
|
|
: NormalTransactionOp(aTransaction)
|
|
, mParams(aParams)
|
|
, mObjectStoreMayHaveIndexes(false)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aTransaction);
|
|
|
|
RefPtr<FullObjectStoreMetadata> metadata =
|
|
aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId());
|
|
MOZ_ASSERT(metadata);
|
|
|
|
mObjectStoreMayHaveIndexes = metadata->HasLiveIndexes();
|
|
}
|
|
|
|
nsresult
|
|
ObjectStoreDeleteRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ObjectStoreDeleteRequestOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
DatabaseConnection::AutoSavepoint autoSave;
|
|
nsresult rv = autoSave.Start(Transaction());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool objectStoreHasIndexes;
|
|
rv = ObjectStoreHasIndexes(this,
|
|
aConnection,
|
|
mParams.objectStoreId(),
|
|
mObjectStoreMayHaveIndexes,
|
|
&objectStoreHasIndexes);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (objectStoreHasIndexes) {
|
|
rv = DeleteObjectStoreDataTableRowsWithIndexes(aConnection,
|
|
mParams.objectStoreId(),
|
|
mParams.keyRange());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
NS_NAMED_LITERAL_CSTRING(objectStoreIdString, "object_store_id");
|
|
|
|
nsAutoCString keyRangeClause;
|
|
GetBindingClauseForKeyRange(mParams.keyRange(),
|
|
NS_LITERAL_CSTRING("key"),
|
|
keyRangeClause);
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
rv = aConnection->GetCachedStatement(
|
|
NS_LITERAL_CSTRING("DELETE FROM object_data "
|
|
"WHERE object_store_id = :") + objectStoreIdString +
|
|
keyRangeClause +
|
|
NS_LITERAL_CSTRING(";"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(objectStoreIdString, mParams.objectStoreId());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = BindKeyRangeToStatement(mParams.keyRange(), stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = autoSave.Commit();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
ObjectStoreClearRequestOp::ObjectStoreClearRequestOp(
|
|
TransactionBase* aTransaction,
|
|
const ObjectStoreClearParams& aParams)
|
|
: NormalTransactionOp(aTransaction)
|
|
, mParams(aParams)
|
|
, mObjectStoreMayHaveIndexes(false)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aTransaction);
|
|
|
|
RefPtr<FullObjectStoreMetadata> metadata =
|
|
aTransaction->GetMetadataForObjectStoreId(mParams.objectStoreId());
|
|
MOZ_ASSERT(metadata);
|
|
|
|
mObjectStoreMayHaveIndexes = metadata->HasLiveIndexes();
|
|
}
|
|
|
|
nsresult
|
|
ObjectStoreClearRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ObjectStoreClearRequestOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
DatabaseConnection::AutoSavepoint autoSave;
|
|
nsresult rv = autoSave.Start(Transaction());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
bool objectStoreHasIndexes;
|
|
rv = ObjectStoreHasIndexes(this,
|
|
aConnection,
|
|
mParams.objectStoreId(),
|
|
mObjectStoreMayHaveIndexes,
|
|
&objectStoreHasIndexes);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (objectStoreHasIndexes) {
|
|
rv = DeleteObjectStoreDataTableRowsWithIndexes(aConnection,
|
|
mParams.objectStoreId(),
|
|
void_t());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
} else {
|
|
DatabaseConnection::CachedStatement stmt;
|
|
rv = aConnection->GetCachedStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM object_data "
|
|
"WHERE object_store_id = :object_store_id;"),
|
|
&stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("object_store_id"),
|
|
mParams.objectStoreId());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = autoSave.Commit();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
ObjectStoreCountRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ObjectStoreCountRequestOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const bool hasKeyRange =
|
|
mParams.optionalKeyRange().type() == OptionalKeyRange::TSerializedKeyRange;
|
|
|
|
nsAutoCString keyRangeClause;
|
|
if (hasKeyRange) {
|
|
GetBindingClauseForKeyRange(
|
|
mParams.optionalKeyRange().get_SerializedKeyRange(),
|
|
NS_LITERAL_CSTRING("key"),
|
|
keyRangeClause);
|
|
}
|
|
|
|
nsCString query =
|
|
NS_LITERAL_CSTRING("SELECT count(*) "
|
|
"FROM object_data "
|
|
"WHERE object_store_id = :osid") +
|
|
keyRangeClause;
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(query, &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("osid"),
|
|
mParams.objectStoreId());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (hasKeyRange) {
|
|
rv = BindKeyRangeToStatement(
|
|
mParams.optionalKeyRange().get_SerializedKeyRange(),
|
|
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;
|
|
}
|
|
|
|
if (NS_WARN_IF(!hasResult)) {
|
|
MOZ_ASSERT(false, "This should never be possible!");
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
int64_t count = stmt->AsInt64(0);
|
|
if (NS_WARN_IF(count < 0)) {
|
|
MOZ_ASSERT(false, "This should never be possible!");
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
mResponse.count() = count;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
already_AddRefed<FullIndexMetadata>
|
|
IndexRequestOpBase::IndexMetadataForParams(TransactionBase* aTransaction,
|
|
const RequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aTransaction);
|
|
MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetParams ||
|
|
aParams.type() == RequestParams::TIndexGetKeyParams ||
|
|
aParams.type() == RequestParams::TIndexGetAllParams ||
|
|
aParams.type() == RequestParams::TIndexGetAllKeysParams ||
|
|
aParams.type() == RequestParams::TIndexCountParams);
|
|
|
|
uint64_t objectStoreId;
|
|
uint64_t indexId;
|
|
|
|
switch (aParams.type()) {
|
|
case RequestParams::TIndexGetParams: {
|
|
const IndexGetParams& params = aParams.get_IndexGetParams();
|
|
objectStoreId = params.objectStoreId();
|
|
indexId = params.indexId();
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TIndexGetKeyParams: {
|
|
const IndexGetKeyParams& params = aParams.get_IndexGetKeyParams();
|
|
objectStoreId = params.objectStoreId();
|
|
indexId = params.indexId();
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TIndexGetAllParams: {
|
|
const IndexGetAllParams& params = aParams.get_IndexGetAllParams();
|
|
objectStoreId = params.objectStoreId();
|
|
indexId = params.indexId();
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TIndexGetAllKeysParams: {
|
|
const IndexGetAllKeysParams& params = aParams.get_IndexGetAllKeysParams();
|
|
objectStoreId = params.objectStoreId();
|
|
indexId = params.indexId();
|
|
break;
|
|
}
|
|
|
|
case RequestParams::TIndexCountParams: {
|
|
const IndexCountParams& params = aParams.get_IndexCountParams();
|
|
objectStoreId = params.objectStoreId();
|
|
indexId = params.indexId();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
const RefPtr<FullObjectStoreMetadata> objectStoreMetadata =
|
|
aTransaction->GetMetadataForObjectStoreId(objectStoreId);
|
|
MOZ_ASSERT(objectStoreMetadata);
|
|
|
|
RefPtr<FullIndexMetadata> indexMetadata =
|
|
aTransaction->GetMetadataForIndexId(objectStoreMetadata, indexId);
|
|
MOZ_ASSERT(indexMetadata);
|
|
|
|
return indexMetadata.forget();
|
|
}
|
|
|
|
IndexGetRequestOp::IndexGetRequestOp(TransactionBase* aTransaction,
|
|
const RequestParams& aParams,
|
|
bool aGetAll)
|
|
: IndexRequestOpBase(aTransaction, aParams)
|
|
, mDatabase(aTransaction->GetDatabase())
|
|
, mOptionalKeyRange(aGetAll ?
|
|
aParams.get_IndexGetAllParams().optionalKeyRange() :
|
|
OptionalKeyRange(aParams.get_IndexGetParams()
|
|
.keyRange()))
|
|
, mBackgroundParent(aTransaction->GetBackgroundParent())
|
|
, mLimit(aGetAll ? aParams.get_IndexGetAllParams().limit() : 1)
|
|
, mGetAll(aGetAll)
|
|
{
|
|
MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetParams ||
|
|
aParams.type() == RequestParams::TIndexGetAllParams);
|
|
MOZ_ASSERT(mDatabase);
|
|
MOZ_ASSERT_IF(!aGetAll,
|
|
mOptionalKeyRange.type() ==
|
|
OptionalKeyRange::TSerializedKeyRange);
|
|
MOZ_ASSERT(mBackgroundParent);
|
|
}
|
|
|
|
nsresult
|
|
IndexGetRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT_IF(!mGetAll,
|
|
mOptionalKeyRange.type() ==
|
|
OptionalKeyRange::TSerializedKeyRange);
|
|
MOZ_ASSERT_IF(!mGetAll, mLimit == 1);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"IndexGetRequestOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const bool hasKeyRange =
|
|
mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
|
|
|
|
nsCString indexTable;
|
|
if (mMetadata->mCommonMetadata.unique()) {
|
|
indexTable.AssignLiteral("unique_index_data ");
|
|
}
|
|
else {
|
|
indexTable.AssignLiteral("index_data ");
|
|
}
|
|
|
|
nsAutoCString keyRangeClause;
|
|
if (hasKeyRange) {
|
|
GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
NS_LITERAL_CSTRING("value"),
|
|
keyRangeClause);
|
|
}
|
|
|
|
nsCString limitClause;
|
|
if (mLimit) {
|
|
limitClause.AssignLiteral(" LIMIT ");
|
|
limitClause.AppendInt(mLimit);
|
|
}
|
|
|
|
nsCString query =
|
|
NS_LITERAL_CSTRING("SELECT file_ids, data "
|
|
"FROM object_data "
|
|
"INNER JOIN ") +
|
|
indexTable +
|
|
NS_LITERAL_CSTRING("AS index_table "
|
|
"ON object_data.object_store_id = "
|
|
"index_table.object_store_id "
|
|
"AND object_data.key = "
|
|
"index_table.object_data_key "
|
|
"WHERE index_id = :index_id") +
|
|
keyRangeClause +
|
|
limitClause;
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(query, &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
|
|
mMetadata->mCommonMetadata.id());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (hasKeyRange) {
|
|
rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
bool hasResult;
|
|
while(NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
|
|
StructuredCloneReadInfo* cloneInfo = mResponse.AppendElement(fallible);
|
|
if (NS_WARN_IF(!cloneInfo)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
rv = GetStructuredCloneReadInfoFromStatement(stmt, 1, 0,
|
|
mDatabase->GetFileManager(),
|
|
cloneInfo);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (cloneInfo->mHasPreprocessInfo) {
|
|
IDB_WARNING("Preprocessing for indexes not yet implemented!");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
IndexGetRequestOp::GetResponse(RequestResponse& aResponse)
|
|
{
|
|
MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
|
|
|
|
if (mGetAll) {
|
|
aResponse = IndexGetAllResponse();
|
|
|
|
if (!mResponse.IsEmpty()) {
|
|
FallibleTArray<SerializedStructuredCloneReadInfo> fallibleCloneInfos;
|
|
if (NS_WARN_IF(!fallibleCloneInfos.SetLength(mResponse.Length(),
|
|
fallible))) {
|
|
aResponse = NS_ERROR_OUT_OF_MEMORY;
|
|
return;
|
|
}
|
|
|
|
for (uint32_t count = mResponse.Length(), index = 0;
|
|
index < count;
|
|
index++) {
|
|
StructuredCloneReadInfo& info = mResponse[index];
|
|
|
|
SerializedStructuredCloneReadInfo& serializedInfo =
|
|
fallibleCloneInfos[index];
|
|
|
|
serializedInfo.data().data = Move(info.mData);
|
|
|
|
FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
|
|
nsresult rv = SerializeStructuredCloneFiles(mBackgroundParent,
|
|
mDatabase,
|
|
info.mFiles,
|
|
/* aForPreprocess */ false,
|
|
serializedFiles);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
aResponse = rv;
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(serializedInfo.files().IsEmpty());
|
|
|
|
serializedInfo.files().SwapElements(serializedFiles);
|
|
}
|
|
|
|
nsTArray<SerializedStructuredCloneReadInfo>& cloneInfos =
|
|
aResponse.get_IndexGetAllResponse().cloneInfos();
|
|
|
|
fallibleCloneInfos.SwapElements(cloneInfos);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
aResponse = IndexGetResponse();
|
|
|
|
if (!mResponse.IsEmpty()) {
|
|
StructuredCloneReadInfo& info = mResponse[0];
|
|
|
|
SerializedStructuredCloneReadInfo& serializedInfo =
|
|
aResponse.get_IndexGetResponse().cloneInfo();
|
|
|
|
serializedInfo.data().data = Move(info.mData);
|
|
|
|
FallibleTArray<SerializedStructuredCloneFile> serializedFiles;
|
|
nsresult rv =
|
|
SerializeStructuredCloneFiles(mBackgroundParent,
|
|
mDatabase,
|
|
info.mFiles,
|
|
/* aForPreprocess */ false,
|
|
serializedFiles);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
aResponse = rv;
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(serializedInfo.files().IsEmpty());
|
|
|
|
serializedInfo.files().SwapElements(serializedFiles);
|
|
}
|
|
}
|
|
|
|
IndexGetKeyRequestOp::IndexGetKeyRequestOp(TransactionBase* aTransaction,
|
|
const RequestParams& aParams,
|
|
bool aGetAll)
|
|
: IndexRequestOpBase(aTransaction, aParams)
|
|
, mOptionalKeyRange(aGetAll ?
|
|
aParams.get_IndexGetAllKeysParams().optionalKeyRange() :
|
|
OptionalKeyRange(aParams.get_IndexGetKeyParams()
|
|
.keyRange()))
|
|
, mLimit(aGetAll ? aParams.get_IndexGetAllKeysParams().limit() : 1)
|
|
, mGetAll(aGetAll)
|
|
{
|
|
MOZ_ASSERT(aParams.type() == RequestParams::TIndexGetKeyParams ||
|
|
aParams.type() == RequestParams::TIndexGetAllKeysParams);
|
|
MOZ_ASSERT_IF(!aGetAll,
|
|
mOptionalKeyRange.type() ==
|
|
OptionalKeyRange::TSerializedKeyRange);
|
|
}
|
|
|
|
nsresult
|
|
IndexGetKeyRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT_IF(!mGetAll,
|
|
mOptionalKeyRange.type() ==
|
|
OptionalKeyRange::TSerializedKeyRange);
|
|
MOZ_ASSERT_IF(!mGetAll, mLimit == 1);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"IndexGetKeyRequestOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const bool hasKeyRange =
|
|
mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
|
|
|
|
nsCString indexTable;
|
|
if (mMetadata->mCommonMetadata.unique()) {
|
|
indexTable.AssignLiteral("unique_index_data ");
|
|
}
|
|
else {
|
|
indexTable.AssignLiteral("index_data ");
|
|
}
|
|
|
|
nsAutoCString keyRangeClause;
|
|
if (hasKeyRange) {
|
|
GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
NS_LITERAL_CSTRING("value"),
|
|
keyRangeClause);
|
|
}
|
|
|
|
nsCString limitClause;
|
|
if (mLimit) {
|
|
limitClause.AssignLiteral(" LIMIT ");
|
|
limitClause.AppendInt(mLimit);
|
|
}
|
|
|
|
nsCString query =
|
|
NS_LITERAL_CSTRING("SELECT object_data_key "
|
|
"FROM ") +
|
|
indexTable +
|
|
NS_LITERAL_CSTRING("WHERE index_id = :index_id") +
|
|
keyRangeClause +
|
|
limitClause;
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(query, &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
|
|
mMetadata->mCommonMetadata.id());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (hasKeyRange) {
|
|
rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
bool hasResult;
|
|
while(NS_SUCCEEDED((rv = stmt->ExecuteStep(&hasResult))) && hasResult) {
|
|
Key* key = mResponse.AppendElement(fallible);
|
|
if (NS_WARN_IF(!key)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
rv = key->SetFromStatement(stmt, 0);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
IndexGetKeyRequestOp::GetResponse(RequestResponse& aResponse)
|
|
{
|
|
MOZ_ASSERT_IF(!mGetAll, mResponse.Length() <= 1);
|
|
|
|
if (mGetAll) {
|
|
aResponse = IndexGetAllKeysResponse();
|
|
|
|
if (!mResponse.IsEmpty()) {
|
|
mResponse.SwapElements(aResponse.get_IndexGetAllKeysResponse().keys());
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
aResponse = IndexGetKeyResponse();
|
|
|
|
if (!mResponse.IsEmpty()) {
|
|
aResponse.get_IndexGetKeyResponse().key() = Move(mResponse[0]);
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
IndexCountRequestOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"IndexCountRequestOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const bool hasKeyRange =
|
|
mParams.optionalKeyRange().type() == OptionalKeyRange::TSerializedKeyRange;
|
|
|
|
nsCString indexTable;
|
|
if (mMetadata->mCommonMetadata.unique()) {
|
|
indexTable.AssignLiteral("unique_index_data ");
|
|
}
|
|
else {
|
|
indexTable.AssignLiteral("index_data ");
|
|
}
|
|
|
|
nsAutoCString keyRangeClause;
|
|
if (hasKeyRange) {
|
|
GetBindingClauseForKeyRange(
|
|
mParams.optionalKeyRange().get_SerializedKeyRange(),
|
|
NS_LITERAL_CSTRING("value"),
|
|
keyRangeClause);
|
|
}
|
|
|
|
nsCString query =
|
|
NS_LITERAL_CSTRING("SELECT count(*) "
|
|
"FROM ") +
|
|
indexTable +
|
|
NS_LITERAL_CSTRING("WHERE index_id = :index_id") +
|
|
keyRangeClause;
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(query, &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("index_id"),
|
|
mMetadata->mCommonMetadata.id());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (hasKeyRange) {
|
|
rv = BindKeyRangeToStatement(
|
|
mParams.optionalKeyRange().get_SerializedKeyRange(),
|
|
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;
|
|
}
|
|
|
|
if (NS_WARN_IF(!hasResult)) {
|
|
MOZ_ASSERT(false, "This should never be possible!");
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
int64_t count = stmt->AsInt64(0);
|
|
if (NS_WARN_IF(count < 0)) {
|
|
MOZ_ASSERT(false, "This should never be possible!");
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
mResponse.count() = count;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
Cursor::
|
|
CursorOpBase::SendFailureResult(nsresult aResultCode)
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(NS_FAILED(aResultCode));
|
|
MOZ_ASSERT(mCursor);
|
|
MOZ_ASSERT(mCursor->mCurrentlyRunningOp == this);
|
|
MOZ_ASSERT(!mResponseSent);
|
|
|
|
if (!IsActorDestroyed()) {
|
|
mResponse = ClampResultCode(aResultCode);
|
|
|
|
// This is an expected race when the transaction is invalidated after
|
|
// data is retrieved from database. We clear the retrieved files to prevent
|
|
// the assertion failure in SendResponseInternal when mResponse.type() is
|
|
// CursorResponse::Tnsresult.
|
|
if (Transaction()->IsInvalidated() && !mFiles.IsEmpty()) {
|
|
mFiles.Clear();
|
|
}
|
|
|
|
mCursor->SendResponseInternal(mResponse, mFiles);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
mResponseSent = true;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void
|
|
Cursor::
|
|
CursorOpBase::Cleanup()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mCursor);
|
|
MOZ_ASSERT_IF(!IsActorDestroyed(), mResponseSent);
|
|
|
|
mCursor = nullptr;
|
|
|
|
#ifdef DEBUG
|
|
// A bit hacky but the CursorOp request is not generated in response to a
|
|
// child request like most other database operations. Do this to make our
|
|
// assertions happy.
|
|
NoteActorDestroyed();
|
|
#endif
|
|
|
|
TransactionDatabaseOperationBase::Cleanup();
|
|
}
|
|
|
|
nsresult
|
|
Cursor::
|
|
CursorOpBase::PopulateResponseFromStatement(
|
|
DatabaseConnection::CachedStatement& aStmt,
|
|
bool aInitializeResponse)
|
|
{
|
|
Transaction()->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mResponse.type() == CursorResponse::T__None);
|
|
MOZ_ASSERT_IF(mFiles.IsEmpty(), aInitializeResponse);
|
|
|
|
nsresult rv = mCursor->mKey.SetFromStatement(aStmt, 0);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
switch (mCursor->mType) {
|
|
case OpenCursorParams::TObjectStoreOpenCursorParams: {
|
|
StructuredCloneReadInfo cloneInfo;
|
|
rv = GetStructuredCloneReadInfoFromStatement(aStmt,
|
|
2,
|
|
1,
|
|
mCursor->mFileManager,
|
|
&cloneInfo);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (cloneInfo.mHasPreprocessInfo) {
|
|
IDB_WARNING("Preprocessing for cursors not yet implemented!");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
if (aInitializeResponse) {
|
|
mResponse = nsTArray<ObjectStoreCursorResponse>();
|
|
} else {
|
|
MOZ_ASSERT(mResponse.type() ==
|
|
CursorResponse::TArrayOfObjectStoreCursorResponse);
|
|
}
|
|
|
|
auto& responses = mResponse.get_ArrayOfObjectStoreCursorResponse();
|
|
auto& response = *responses.AppendElement();
|
|
response.cloneInfo().data().data = Move(cloneInfo.mData);
|
|
response.key() = mCursor->mKey;
|
|
|
|
mFiles.AppendElement(Move(cloneInfo.mFiles));
|
|
break;
|
|
}
|
|
|
|
case OpenCursorParams::TObjectStoreOpenKeyCursorParams: {
|
|
MOZ_ASSERT(aInitializeResponse);
|
|
mResponse = ObjectStoreKeyCursorResponse(mCursor->mKey);
|
|
break;
|
|
}
|
|
|
|
case OpenCursorParams::TIndexOpenCursorParams: {
|
|
rv = mCursor->mSortKey.SetFromStatement(aStmt, 1);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = mCursor->mObjectKey.SetFromStatement(aStmt, 2);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
StructuredCloneReadInfo cloneInfo;
|
|
rv = GetStructuredCloneReadInfoFromStatement(aStmt,
|
|
4,
|
|
3,
|
|
mCursor->mFileManager,
|
|
&cloneInfo);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (cloneInfo.mHasPreprocessInfo) {
|
|
IDB_WARNING("Preprocessing for cursors not yet implemented!");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
MOZ_ASSERT(aInitializeResponse);
|
|
mResponse = IndexCursorResponse();
|
|
|
|
auto& response = mResponse.get_IndexCursorResponse();
|
|
response.cloneInfo().data().data = Move(cloneInfo.mData);
|
|
response.key() = mCursor->mKey;
|
|
response.sortKey() = mCursor->mSortKey;
|
|
response.objectKey() = mCursor->mObjectKey;
|
|
|
|
mFiles.AppendElement(Move(cloneInfo.mFiles));
|
|
break;
|
|
}
|
|
|
|
case OpenCursorParams::TIndexOpenKeyCursorParams: {
|
|
rv = mCursor->mSortKey.SetFromStatement(aStmt, 1);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = mCursor->mObjectKey.SetFromStatement(aStmt, 2);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(aInitializeResponse);
|
|
mResponse = IndexKeyCursorResponse(mCursor->mKey,
|
|
mCursor->mSortKey,
|
|
mCursor->mObjectKey);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
Cursor::
|
|
OpenOp::GetRangeKeyInfo(bool aLowerBound, Key* aKey, bool* aOpen)
|
|
{
|
|
AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(aKey);
|
|
MOZ_ASSERT(aKey->IsUnset());
|
|
MOZ_ASSERT(aOpen);
|
|
|
|
if (mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange) {
|
|
const SerializedKeyRange& range =
|
|
mOptionalKeyRange.get_SerializedKeyRange();
|
|
if (range.isOnly()) {
|
|
*aKey = range.lower();
|
|
*aOpen = false;
|
|
#ifdef ENABLE_INTL_API
|
|
if (mCursor->IsLocaleAware()) {
|
|
range.lower().ToLocaleBasedKey(*aKey, mCursor->mLocale);
|
|
}
|
|
#endif
|
|
} else {
|
|
*aKey = aLowerBound ? range.lower() : range.upper();
|
|
*aOpen = aLowerBound ? range.lowerOpen() : range.upperOpen();
|
|
#ifdef ENABLE_INTL_API
|
|
if (mCursor->IsLocaleAware()) {
|
|
if (aLowerBound) {
|
|
range.lower().ToLocaleBasedKey(*aKey, mCursor->mLocale);
|
|
} else {
|
|
range.upper().ToLocaleBasedKey(*aKey, mCursor->mLocale);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
} else {
|
|
*aOpen = false;
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
Cursor::
|
|
OpenOp::DoObjectStoreDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mCursor);
|
|
MOZ_ASSERT(mCursor->mType == OpenCursorParams::TObjectStoreOpenCursorParams);
|
|
MOZ_ASSERT(mCursor->mObjectStoreId);
|
|
MOZ_ASSERT(mCursor->mFileManager);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"Cursor::OpenOp::DoObjectStoreDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const bool usingKeyRange =
|
|
mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
|
|
|
|
NS_NAMED_LITERAL_CSTRING(keyString, "key");
|
|
NS_NAMED_LITERAL_CSTRING(id, "id");
|
|
NS_NAMED_LITERAL_CSTRING(openLimit, " LIMIT ");
|
|
|
|
nsCString queryStart =
|
|
NS_LITERAL_CSTRING("SELECT ") +
|
|
keyString +
|
|
NS_LITERAL_CSTRING(", file_ids, data "
|
|
"FROM object_data "
|
|
"WHERE object_store_id = :") +
|
|
id;
|
|
|
|
nsAutoCString keyRangeClause;
|
|
if (usingKeyRange) {
|
|
GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
keyString,
|
|
keyRangeClause);
|
|
}
|
|
|
|
nsAutoCString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + keyString;
|
|
switch (mCursor->mDirection) {
|
|
case IDBCursor::NEXT:
|
|
case IDBCursor::NEXT_UNIQUE:
|
|
directionClause.AppendLiteral(" ASC");
|
|
break;
|
|
|
|
case IDBCursor::PREV:
|
|
case IDBCursor::PREV_UNIQUE:
|
|
directionClause.AppendLiteral(" DESC");
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
// Note: Changing the number or order of SELECT columns in the query will
|
|
// require changes to CursorOpBase::PopulateResponseFromStatement.
|
|
nsCString firstQuery =
|
|
queryStart +
|
|
keyRangeClause +
|
|
directionClause +
|
|
openLimit +
|
|
NS_LITERAL_CSTRING("1");
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(firstQuery, &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(id, mCursor->mObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (usingKeyRange) {
|
|
rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
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;
|
|
}
|
|
|
|
if (!hasResult) {
|
|
mResponse = void_t();
|
|
return NS_OK;
|
|
}
|
|
|
|
rv = PopulateResponseFromStatement(stmt, true);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Now we need to make the query to get the next match.
|
|
keyRangeClause.Truncate();
|
|
nsAutoCString continueToKeyRangeClause;
|
|
|
|
NS_NAMED_LITERAL_CSTRING(currentKey, "current_key");
|
|
NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key");
|
|
|
|
switch (mCursor->mDirection) {
|
|
case IDBCursor::NEXT:
|
|
case IDBCursor::NEXT_UNIQUE: {
|
|
Key upper;
|
|
bool open;
|
|
GetRangeKeyInfo(false, &upper, &open);
|
|
AppendConditionClause(keyString, currentKey, false, false,
|
|
keyRangeClause);
|
|
AppendConditionClause(keyString, currentKey, false, true,
|
|
continueToKeyRangeClause);
|
|
if (usingKeyRange && !upper.IsUnset()) {
|
|
AppendConditionClause(keyString, rangeKey, true, !open, keyRangeClause);
|
|
AppendConditionClause(keyString, rangeKey, true, !open,
|
|
continueToKeyRangeClause);
|
|
mCursor->mRangeKey = upper;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IDBCursor::PREV:
|
|
case IDBCursor::PREV_UNIQUE: {
|
|
Key lower;
|
|
bool open;
|
|
GetRangeKeyInfo(true, &lower, &open);
|
|
AppendConditionClause(keyString, currentKey, true, false, keyRangeClause);
|
|
AppendConditionClause(keyString, currentKey, true, true,
|
|
continueToKeyRangeClause);
|
|
if (usingKeyRange && !lower.IsUnset()) {
|
|
AppendConditionClause(keyString, rangeKey, false, !open,
|
|
keyRangeClause);
|
|
AppendConditionClause(keyString, rangeKey, false, !open,
|
|
continueToKeyRangeClause);
|
|
mCursor->mRangeKey = lower;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
mCursor->mContinueQuery =
|
|
queryStart +
|
|
keyRangeClause +
|
|
directionClause +
|
|
openLimit;
|
|
|
|
mCursor->mContinueToQuery =
|
|
queryStart +
|
|
continueToKeyRangeClause +
|
|
directionClause +
|
|
openLimit;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Cursor::
|
|
OpenOp::DoObjectStoreKeyDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mCursor);
|
|
MOZ_ASSERT(mCursor->mType ==
|
|
OpenCursorParams::TObjectStoreOpenKeyCursorParams);
|
|
MOZ_ASSERT(mCursor->mObjectStoreId);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"Cursor::OpenOp::DoObjectStoreKeyDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const bool usingKeyRange =
|
|
mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
|
|
|
|
NS_NAMED_LITERAL_CSTRING(keyString, "key");
|
|
NS_NAMED_LITERAL_CSTRING(id, "id");
|
|
NS_NAMED_LITERAL_CSTRING(openLimit, " LIMIT ");
|
|
|
|
nsCString queryStart =
|
|
NS_LITERAL_CSTRING("SELECT ") +
|
|
keyString +
|
|
NS_LITERAL_CSTRING(" FROM object_data "
|
|
"WHERE object_store_id = :") +
|
|
id;
|
|
|
|
nsAutoCString keyRangeClause;
|
|
if (usingKeyRange) {
|
|
GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
keyString,
|
|
keyRangeClause);
|
|
}
|
|
|
|
nsAutoCString directionClause = NS_LITERAL_CSTRING(" ORDER BY ") + keyString;
|
|
switch (mCursor->mDirection) {
|
|
case IDBCursor::NEXT:
|
|
case IDBCursor::NEXT_UNIQUE:
|
|
directionClause.AppendLiteral(" ASC");
|
|
break;
|
|
|
|
case IDBCursor::PREV:
|
|
case IDBCursor::PREV_UNIQUE:
|
|
directionClause.AppendLiteral(" DESC");
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
// Note: Changing the number or order of SELECT columns in the query will
|
|
// require changes to CursorOpBase::PopulateResponseFromStatement.
|
|
nsCString firstQuery =
|
|
queryStart +
|
|
keyRangeClause +
|
|
directionClause +
|
|
openLimit +
|
|
NS_LITERAL_CSTRING("1");
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(firstQuery, &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(id, mCursor->mObjectStoreId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (usingKeyRange) {
|
|
rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
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;
|
|
}
|
|
|
|
if (!hasResult) {
|
|
mResponse = void_t();
|
|
return NS_OK;
|
|
}
|
|
|
|
rv = PopulateResponseFromStatement(stmt, true);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Now we need to make the query to get the next match.
|
|
keyRangeClause.Truncate();
|
|
nsAutoCString continueToKeyRangeClause;
|
|
|
|
NS_NAMED_LITERAL_CSTRING(currentKey, "current_key");
|
|
NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key");
|
|
|
|
switch (mCursor->mDirection) {
|
|
case IDBCursor::NEXT:
|
|
case IDBCursor::NEXT_UNIQUE: {
|
|
Key upper;
|
|
bool open;
|
|
GetRangeKeyInfo(false, &upper, &open);
|
|
AppendConditionClause(keyString, currentKey, false, false,
|
|
keyRangeClause);
|
|
AppendConditionClause(keyString, currentKey, false, true,
|
|
continueToKeyRangeClause);
|
|
if (usingKeyRange && !upper.IsUnset()) {
|
|
AppendConditionClause(keyString, rangeKey, true, !open, keyRangeClause);
|
|
AppendConditionClause(keyString, rangeKey, true, !open,
|
|
continueToKeyRangeClause);
|
|
mCursor->mRangeKey = upper;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case IDBCursor::PREV:
|
|
case IDBCursor::PREV_UNIQUE: {
|
|
Key lower;
|
|
bool open;
|
|
GetRangeKeyInfo(true, &lower, &open);
|
|
AppendConditionClause(keyString, currentKey, true, false, keyRangeClause);
|
|
AppendConditionClause(keyString, currentKey, true, true,
|
|
continueToKeyRangeClause);
|
|
if (usingKeyRange && !lower.IsUnset()) {
|
|
AppendConditionClause(keyString, rangeKey, false, !open,
|
|
keyRangeClause);
|
|
AppendConditionClause(keyString, rangeKey, false, !open,
|
|
continueToKeyRangeClause);
|
|
mCursor->mRangeKey = lower;
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
mCursor->mContinueQuery =
|
|
queryStart +
|
|
keyRangeClause +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinueToQuery =
|
|
queryStart +
|
|
continueToKeyRangeClause +
|
|
directionClause +
|
|
openLimit;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Cursor::
|
|
OpenOp::DoIndexDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mCursor);
|
|
MOZ_ASSERT(mCursor->mType == OpenCursorParams::TIndexOpenCursorParams);
|
|
MOZ_ASSERT(mCursor->mObjectStoreId);
|
|
MOZ_ASSERT(mCursor->mIndexId);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"Cursor::OpenOp::DoIndexDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const bool usingKeyRange =
|
|
mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
|
|
|
|
nsCString indexTable = mCursor->mUniqueIndex ?
|
|
NS_LITERAL_CSTRING("unique_index_data") :
|
|
NS_LITERAL_CSTRING("index_data");
|
|
|
|
NS_NAMED_LITERAL_CSTRING(sortColumn, "sort_column");
|
|
NS_NAMED_LITERAL_CSTRING(id, "id");
|
|
NS_NAMED_LITERAL_CSTRING(openLimit, " LIMIT ");
|
|
|
|
nsAutoCString sortColumnAlias;
|
|
if (mCursor->IsLocaleAware()) {
|
|
sortColumnAlias = "SELECT index_table.value, "
|
|
"index_table.value_locale as sort_column, ";
|
|
} else {
|
|
sortColumnAlias = "SELECT index_table.value as sort_column, "
|
|
"index_table.value_locale, ";
|
|
}
|
|
|
|
nsAutoCString queryStart =
|
|
sortColumnAlias +
|
|
NS_LITERAL_CSTRING( "index_table.object_data_key, "
|
|
"object_data.file_ids, "
|
|
"object_data.data "
|
|
"FROM ") +
|
|
indexTable +
|
|
NS_LITERAL_CSTRING(" AS index_table "
|
|
"JOIN object_data "
|
|
"ON index_table.object_store_id = "
|
|
"object_data.object_store_id "
|
|
"AND index_table.object_data_key = "
|
|
"object_data.key "
|
|
"WHERE index_table.index_id = :") +
|
|
id;
|
|
|
|
nsAutoCString keyRangeClause;
|
|
if (usingKeyRange) {
|
|
GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
sortColumn,
|
|
keyRangeClause);
|
|
}
|
|
|
|
nsAutoCString directionClause =
|
|
NS_LITERAL_CSTRING(" ORDER BY ") +
|
|
sortColumn;
|
|
|
|
switch (mCursor->mDirection) {
|
|
case IDBCursor::NEXT:
|
|
case IDBCursor::NEXT_UNIQUE:
|
|
directionClause.AppendLiteral(" ASC, index_table.object_data_key ASC");
|
|
break;
|
|
|
|
case IDBCursor::PREV:
|
|
directionClause.AppendLiteral(" DESC, index_table.object_data_key DESC");
|
|
break;
|
|
|
|
case IDBCursor::PREV_UNIQUE:
|
|
directionClause.AppendLiteral(" DESC, index_table.object_data_key ASC");
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
// Note: Changing the number or order of SELECT columns in the query will
|
|
// require changes to CursorOpBase::PopulateResponseFromStatement.
|
|
nsCString firstQuery =
|
|
queryStart +
|
|
keyRangeClause +
|
|
directionClause +
|
|
openLimit +
|
|
NS_LITERAL_CSTRING("1");
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(firstQuery, &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(id, mCursor->mIndexId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (usingKeyRange) {
|
|
if (mCursor->IsLocaleAware()) {
|
|
rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
stmt,
|
|
mCursor->mLocale);
|
|
} else {
|
|
rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
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;
|
|
}
|
|
|
|
if (!hasResult) {
|
|
mResponse = void_t();
|
|
return NS_OK;
|
|
}
|
|
|
|
rv = PopulateResponseFromStatement(stmt, true);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Now we need to make the query to get the next match.
|
|
NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key");
|
|
|
|
switch (mCursor->mDirection) {
|
|
case IDBCursor::NEXT: {
|
|
Key upper;
|
|
bool open;
|
|
GetRangeKeyInfo(false, &upper, &open);
|
|
if (usingKeyRange && !upper.IsUnset()) {
|
|
AppendConditionClause(sortColumn, rangeKey, true, !open, queryStart);
|
|
mCursor->mRangeKey = upper;
|
|
}
|
|
mCursor->mContinueQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column >= :current_key "
|
|
"AND ( sort_column > :current_key OR "
|
|
"index_table.object_data_key > :object_key ) "
|
|
) +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinueToQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column >= :current_key") +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinuePrimaryKeyQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column >= :current_key "
|
|
"AND index_table.object_data_key >= :object_key "
|
|
) +
|
|
directionClause +
|
|
openLimit;
|
|
break;
|
|
}
|
|
|
|
case IDBCursor::NEXT_UNIQUE: {
|
|
Key upper;
|
|
bool open;
|
|
GetRangeKeyInfo(false, &upper, &open);
|
|
if (usingKeyRange && !upper.IsUnset()) {
|
|
AppendConditionClause(sortColumn, rangeKey, true, !open, queryStart);
|
|
mCursor->mRangeKey = upper;
|
|
}
|
|
mCursor->mContinueQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column > :current_key") +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinueToQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column >= :current_key") +
|
|
directionClause +
|
|
openLimit;
|
|
break;
|
|
}
|
|
|
|
case IDBCursor::PREV: {
|
|
Key lower;
|
|
bool open;
|
|
GetRangeKeyInfo(true, &lower, &open);
|
|
if (usingKeyRange && !lower.IsUnset()) {
|
|
AppendConditionClause(sortColumn, rangeKey, false, !open, queryStart);
|
|
mCursor->mRangeKey = lower;
|
|
}
|
|
mCursor->mContinueQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column <= :current_key "
|
|
"AND ( sort_column < :current_key OR "
|
|
"index_table.object_data_key < :object_key ) "
|
|
) +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinueToQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column <= :current_key") +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinuePrimaryKeyQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column <= :current_key "
|
|
"AND index_table.object_data_key <= :object_key "
|
|
) +
|
|
directionClause +
|
|
openLimit;
|
|
break;
|
|
}
|
|
|
|
case IDBCursor::PREV_UNIQUE: {
|
|
Key lower;
|
|
bool open;
|
|
GetRangeKeyInfo(true, &lower, &open);
|
|
if (usingKeyRange && !lower.IsUnset()) {
|
|
AppendConditionClause(sortColumn, rangeKey, false, !open, queryStart);
|
|
mCursor->mRangeKey = lower;
|
|
}
|
|
mCursor->mContinueQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column < :current_key") +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinueToQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column <= :current_key") +
|
|
directionClause +
|
|
openLimit;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Cursor::
|
|
OpenOp::DoIndexKeyDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mCursor);
|
|
MOZ_ASSERT(mCursor->mType == OpenCursorParams::TIndexOpenKeyCursorParams);
|
|
MOZ_ASSERT(mCursor->mObjectStoreId);
|
|
MOZ_ASSERT(mCursor->mIndexId);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"Cursor::OpenOp::DoIndexKeyDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const bool usingKeyRange =
|
|
mOptionalKeyRange.type() == OptionalKeyRange::TSerializedKeyRange;
|
|
|
|
nsCString table = mCursor->mUniqueIndex ?
|
|
NS_LITERAL_CSTRING("unique_index_data") :
|
|
NS_LITERAL_CSTRING("index_data");
|
|
|
|
NS_NAMED_LITERAL_CSTRING(sortColumn, "sort_column");
|
|
NS_NAMED_LITERAL_CSTRING(id, "id");
|
|
NS_NAMED_LITERAL_CSTRING(openLimit, " LIMIT ");
|
|
|
|
nsAutoCString sortColumnAlias;
|
|
if (mCursor->IsLocaleAware()) {
|
|
sortColumnAlias = "SELECT value, "
|
|
"value_locale as sort_column, ";
|
|
} else {
|
|
sortColumnAlias = "SELECT value as sort_column, "
|
|
"value_locale, ";
|
|
}
|
|
|
|
nsAutoCString queryStart =
|
|
sortColumnAlias +
|
|
NS_LITERAL_CSTRING( "object_data_key "
|
|
" FROM ") +
|
|
table +
|
|
NS_LITERAL_CSTRING(" WHERE index_id = :") +
|
|
id;
|
|
|
|
nsAutoCString keyRangeClause;
|
|
if (usingKeyRange) {
|
|
GetBindingClauseForKeyRange(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
sortColumn,
|
|
keyRangeClause);
|
|
}
|
|
|
|
nsAutoCString directionClause =
|
|
NS_LITERAL_CSTRING(" ORDER BY ") +
|
|
sortColumn;
|
|
|
|
switch (mCursor->mDirection) {
|
|
case IDBCursor::NEXT:
|
|
case IDBCursor::NEXT_UNIQUE:
|
|
directionClause.AppendLiteral(" ASC, object_data_key ASC");
|
|
break;
|
|
|
|
case IDBCursor::PREV:
|
|
directionClause.AppendLiteral(" DESC, object_data_key DESC");
|
|
break;
|
|
|
|
case IDBCursor::PREV_UNIQUE:
|
|
directionClause.AppendLiteral(" DESC, object_data_key ASC");
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
// Note: Changing the number or order of SELECT columns in the query will
|
|
// require changes to CursorOpBase::PopulateResponseFromStatement.
|
|
nsCString firstQuery =
|
|
queryStart +
|
|
keyRangeClause +
|
|
directionClause +
|
|
openLimit +
|
|
NS_LITERAL_CSTRING("1");
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(firstQuery, &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64ByName(id, mCursor->mIndexId);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (usingKeyRange) {
|
|
if (mCursor->IsLocaleAware()) {
|
|
rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
stmt,
|
|
mCursor->mLocale);
|
|
} else {
|
|
rv = BindKeyRangeToStatement(mOptionalKeyRange.get_SerializedKeyRange(),
|
|
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;
|
|
}
|
|
|
|
if (!hasResult) {
|
|
mResponse = void_t();
|
|
return NS_OK;
|
|
}
|
|
|
|
rv = PopulateResponseFromStatement(stmt, true);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Now we need to make the query to get the next match.
|
|
NS_NAMED_LITERAL_CSTRING(rangeKey, "range_key");
|
|
|
|
switch (mCursor->mDirection) {
|
|
case IDBCursor::NEXT: {
|
|
Key upper;
|
|
bool open;
|
|
GetRangeKeyInfo(false, &upper, &open);
|
|
if (usingKeyRange && !upper.IsUnset()) {
|
|
AppendConditionClause(sortColumn, rangeKey, true, !open, queryStart);
|
|
mCursor->mRangeKey = upper;
|
|
}
|
|
mCursor->mContinueQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column >= :current_key "
|
|
"AND ( sort_column > :current_key OR "
|
|
"object_data_key > :object_key )") +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinueToQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column >= :current_key ") +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinuePrimaryKeyQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column >= :current_key "
|
|
"AND object_data_key >= :object_key "
|
|
) +
|
|
directionClause +
|
|
openLimit;
|
|
break;
|
|
}
|
|
|
|
case IDBCursor::NEXT_UNIQUE: {
|
|
Key upper;
|
|
bool open;
|
|
GetRangeKeyInfo(false, &upper, &open);
|
|
if (usingKeyRange && !upper.IsUnset()) {
|
|
AppendConditionClause(sortColumn, rangeKey, true, !open, queryStart);
|
|
mCursor->mRangeKey = upper;
|
|
}
|
|
mCursor->mContinueQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column > :current_key") +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinueToQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column >= :current_key") +
|
|
directionClause +
|
|
openLimit;
|
|
break;
|
|
}
|
|
|
|
case IDBCursor::PREV: {
|
|
Key lower;
|
|
bool open;
|
|
GetRangeKeyInfo(true, &lower, &open);
|
|
if (usingKeyRange && !lower.IsUnset()) {
|
|
AppendConditionClause(sortColumn, rangeKey, false, !open, queryStart);
|
|
mCursor->mRangeKey = lower;
|
|
}
|
|
|
|
mCursor->mContinueQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column <= :current_key "
|
|
"AND ( sort_column < :current_key OR "
|
|
"object_data_key < :object_key )") +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinueToQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column <= :current_key ") +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinuePrimaryKeyQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column <= :current_key "
|
|
"AND object_data_key <= :object_key "
|
|
) +
|
|
directionClause +
|
|
openLimit;
|
|
break;
|
|
}
|
|
|
|
case IDBCursor::PREV_UNIQUE: {
|
|
Key lower;
|
|
bool open;
|
|
GetRangeKeyInfo(true, &lower, &open);
|
|
if (usingKeyRange && !lower.IsUnset()) {
|
|
AppendConditionClause(sortColumn, rangeKey, false, !open, queryStart);
|
|
mCursor->mRangeKey = lower;
|
|
}
|
|
mCursor->mContinueQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column < :current_key") +
|
|
directionClause +
|
|
openLimit;
|
|
mCursor->mContinueToQuery =
|
|
queryStart +
|
|
NS_LITERAL_CSTRING(" AND sort_column <= :current_key") +
|
|
directionClause +
|
|
openLimit;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Cursor::
|
|
OpenOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mCursor);
|
|
MOZ_ASSERT(mCursor->mContinueQuery.IsEmpty());
|
|
MOZ_ASSERT(mCursor->mContinueToQuery.IsEmpty());
|
|
MOZ_ASSERT(mCursor->mContinuePrimaryKeyQuery.IsEmpty());
|
|
MOZ_ASSERT(mCursor->mKey.IsUnset());
|
|
MOZ_ASSERT(mCursor->mRangeKey.IsUnset());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"Cursor::OpenOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv;
|
|
|
|
switch (mCursor->mType) {
|
|
case OpenCursorParams::TObjectStoreOpenCursorParams:
|
|
rv = DoObjectStoreDatabaseWork(aConnection);
|
|
break;
|
|
|
|
case OpenCursorParams::TObjectStoreOpenKeyCursorParams:
|
|
rv = DoObjectStoreKeyDatabaseWork(aConnection);
|
|
break;
|
|
|
|
case OpenCursorParams::TIndexOpenCursorParams:
|
|
rv = DoIndexDatabaseWork(aConnection);
|
|
break;
|
|
|
|
case OpenCursorParams::TIndexOpenKeyCursorParams:
|
|
rv = DoIndexKeyDatabaseWork(aConnection);
|
|
break;
|
|
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Cursor::
|
|
OpenOp::SendSuccessResult()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mCursor);
|
|
MOZ_ASSERT(mCursor->mCurrentlyRunningOp == this);
|
|
MOZ_ASSERT(mResponse.type() != CursorResponse::T__None);
|
|
MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
|
|
mCursor->mKey.IsUnset());
|
|
MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
|
|
mCursor->mSortKey.IsUnset());
|
|
MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
|
|
mCursor->mRangeKey.IsUnset());
|
|
MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
|
|
mCursor->mObjectKey.IsUnset());
|
|
|
|
if (IsActorDestroyed()) {
|
|
return NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
|
|
}
|
|
|
|
mCursor->SendResponseInternal(mResponse, mFiles);
|
|
|
|
#ifdef DEBUG
|
|
mResponseSent = true;
|
|
#endif
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Cursor::
|
|
ContinueOp::DoDatabaseWork(DatabaseConnection* aConnection)
|
|
{
|
|
MOZ_ASSERT(aConnection);
|
|
aConnection->AssertIsOnConnectionThread();
|
|
MOZ_ASSERT(mCursor);
|
|
MOZ_ASSERT(mCursor->mObjectStoreId);
|
|
MOZ_ASSERT(!mCursor->mContinueQuery.IsEmpty());
|
|
MOZ_ASSERT(!mCursor->mContinueToQuery.IsEmpty());
|
|
MOZ_ASSERT(!mCursor->mKey.IsUnset());
|
|
|
|
const bool isIndex =
|
|
mCursor->mType == OpenCursorParams::TIndexOpenCursorParams ||
|
|
mCursor->mType == OpenCursorParams::TIndexOpenKeyCursorParams;
|
|
|
|
MOZ_ASSERT_IF(isIndex &&
|
|
(mCursor->mDirection == IDBCursor::NEXT ||
|
|
mCursor->mDirection == IDBCursor::PREV),
|
|
!mCursor->mContinuePrimaryKeyQuery.IsEmpty());
|
|
MOZ_ASSERT_IF(isIndex, mCursor->mIndexId);
|
|
MOZ_ASSERT_IF(isIndex, !mCursor->mObjectKey.IsUnset());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"Cursor::ContinueOp::DoDatabaseWork",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// We need to pick a query based on whether or not a key was passed to the
|
|
// continue function. If not we'll grab the the next item in the database that
|
|
// is greater than (or less than, if we're running a PREV cursor) the current
|
|
// key. If a key was passed we'll grab the next item in the database that is
|
|
// greater than (or less than, if we're running a PREV cursor) or equal to the
|
|
// key that was specified.
|
|
|
|
// Note: Changing the number or order of SELECT columns in the query will
|
|
// require changes to CursorOpBase::PopulateResponseFromStatement.
|
|
bool hasContinueKey = false;
|
|
bool hasContinuePrimaryKey = false;
|
|
uint32_t advanceCount = 1;
|
|
Key& currentKey = mCursor->IsLocaleAware() ? mCursor->mSortKey : mCursor->mKey;
|
|
|
|
switch (mParams.type()) {
|
|
case CursorRequestParams::TContinueParams:
|
|
if (!mParams.get_ContinueParams().key().IsUnset()) {
|
|
hasContinueKey = true;
|
|
currentKey = mParams.get_ContinueParams().key();
|
|
}
|
|
break;
|
|
case CursorRequestParams::TContinuePrimaryKeyParams:
|
|
MOZ_ASSERT(!mParams.get_ContinuePrimaryKeyParams().key().IsUnset());
|
|
MOZ_ASSERT(!mParams.get_ContinuePrimaryKeyParams().primaryKey().IsUnset());
|
|
MOZ_ASSERT(mCursor->mDirection == IDBCursor::NEXT ||
|
|
mCursor->mDirection == IDBCursor::PREV);
|
|
hasContinueKey = true;
|
|
hasContinuePrimaryKey = true;
|
|
currentKey = mParams.get_ContinuePrimaryKeyParams().key();
|
|
break;
|
|
case CursorRequestParams::TAdvanceParams:
|
|
advanceCount = mParams.get_AdvanceParams().count();
|
|
break;
|
|
default:
|
|
MOZ_CRASH("Should never get here!");
|
|
}
|
|
|
|
const nsCString& continueQuery =
|
|
hasContinuePrimaryKey ? mCursor->mContinuePrimaryKeyQuery :
|
|
hasContinueKey ? mCursor->mContinueToQuery : mCursor->mContinueQuery;
|
|
|
|
MOZ_ASSERT(advanceCount > 0);
|
|
nsAutoCString countString;
|
|
countString.AppendInt(advanceCount);
|
|
|
|
nsCString query = continueQuery + countString;
|
|
|
|
NS_NAMED_LITERAL_CSTRING(currentKeyName, "current_key");
|
|
NS_NAMED_LITERAL_CSTRING(rangeKeyName, "range_key");
|
|
NS_NAMED_LITERAL_CSTRING(objectKeyName, "object_key");
|
|
|
|
const bool usingRangeKey = !mCursor->mRangeKey.IsUnset();
|
|
|
|
DatabaseConnection::CachedStatement stmt;
|
|
nsresult rv = aConnection->GetCachedStatement(query, &stmt);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
int64_t id = isIndex ? mCursor->mIndexId : mCursor->mObjectStoreId;
|
|
|
|
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("id"), id);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Bind current key.
|
|
rv = currentKey.BindToStatement(stmt, currentKeyName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Bind range key if it is specified.
|
|
if (usingRangeKey) {
|
|
rv = mCursor->mRangeKey.BindToStatement(stmt, rangeKeyName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Bind object key if duplicates are allowed and we're not continuing to a
|
|
// specific key.
|
|
if (isIndex &&
|
|
!hasContinueKey &&
|
|
(mCursor->mDirection == IDBCursor::NEXT ||
|
|
mCursor->mDirection == IDBCursor::PREV)) {
|
|
rv = mCursor->mObjectKey.BindToStatement(stmt, objectKeyName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Bind object key if primaryKey is specified.
|
|
if (hasContinuePrimaryKey) {
|
|
rv = mParams.get_ContinuePrimaryKeyParams().primaryKey()
|
|
.BindToStatement(stmt, objectKeyName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
|
|
bool hasResult;
|
|
for (uint32_t index = 0; index < advanceCount; index++) {
|
|
rv = stmt->ExecuteStep(&hasResult);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!hasResult) {
|
|
mCursor->mKey.Unset();
|
|
mCursor->mSortKey.Unset();
|
|
mCursor->mRangeKey.Unset();
|
|
mCursor->mObjectKey.Unset();
|
|
mResponse = void_t();
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
rv = PopulateResponseFromStatement(stmt, true);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
Cursor::
|
|
ContinueOp::SendSuccessResult()
|
|
{
|
|
AssertIsOnOwningThread();
|
|
MOZ_ASSERT(mCursor);
|
|
MOZ_ASSERT(mCursor->mCurrentlyRunningOp == this);
|
|
MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
|
|
mCursor->mKey.IsUnset());
|
|
MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
|
|
mCursor->mRangeKey.IsUnset());
|
|
MOZ_ASSERT_IF(mResponse.type() == CursorResponse::Tvoid_t,
|
|
mCursor->mObjectKey.IsUnset());
|
|
|
|
if (IsActorDestroyed()) {
|
|
return NS_ERROR_DOM_INDEXEDDB_ABORT_ERR;
|
|
}
|
|
|
|
mCursor->SendResponseInternal(mResponse, mFiles);
|
|
|
|
#ifdef DEBUG
|
|
mResponseSent = true;
|
|
#endif
|
|
return NS_OK;
|
|
}
|
|
|
|
Utils::Utils()
|
|
#ifdef DEBUG
|
|
: mActorDestroyed(false)
|
|
#endif
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
}
|
|
|
|
Utils::~Utils()
|
|
{
|
|
MOZ_ASSERT(mActorDestroyed);
|
|
}
|
|
|
|
void
|
|
Utils::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
#ifdef DEBUG
|
|
mActorDestroyed = true;
|
|
#endif
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
Utils::RecvDeleteMe()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
IProtocol* mgr = Manager();
|
|
if (!PBackgroundIndexedDBUtilsParent::Send__delete__(this)) {
|
|
return IPC_FAIL_NO_REASON(mgr);
|
|
}
|
|
return IPC_OK();
|
|
}
|
|
|
|
mozilla::ipc::IPCResult
|
|
Utils::RecvGetFileReferences(const PersistenceType& aPersistenceType,
|
|
const nsCString& aOrigin,
|
|
const nsString& aDatabaseName,
|
|
const int64_t& aFileId,
|
|
int32_t* aRefCnt,
|
|
int32_t* aDBRefCnt,
|
|
int32_t* aSliceRefCnt,
|
|
bool* aResult)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aRefCnt);
|
|
MOZ_ASSERT(aDBRefCnt);
|
|
MOZ_ASSERT(aSliceRefCnt);
|
|
MOZ_ASSERT(aResult);
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
if (NS_WARN_IF(!IndexedDatabaseManager::Get() ||
|
|
!QuotaManager::Get())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(!IndexedDatabaseManager::InTestingMode())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(aPersistenceType != quota::PERSISTENCE_TYPE_PERSISTENT &&
|
|
aPersistenceType != quota::PERSISTENCE_TYPE_TEMPORARY &&
|
|
aPersistenceType != quota::PERSISTENCE_TYPE_DEFAULT)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(aOrigin.IsEmpty())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(aDatabaseName.IsEmpty())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
if (NS_WARN_IF(aFileId == 0)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
RefPtr<GetFileReferencesHelper> helper =
|
|
new GetFileReferencesHelper(aPersistenceType, aOrigin, aDatabaseName,
|
|
aFileId);
|
|
|
|
nsresult rv =
|
|
helper->DispatchAndReturnFileReferences(aRefCnt, aDBRefCnt,
|
|
aSliceRefCnt, aResult);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return IPC_FAIL_NO_REASON(this);
|
|
}
|
|
|
|
return IPC_OK();
|
|
}
|
|
|
|
nsresult
|
|
GetFileReferencesHelper::DispatchAndReturnFileReferences(int32_t* aMemRefCnt,
|
|
int32_t* aDBRefCnt,
|
|
int32_t* aSliceRefCnt,
|
|
bool* aResult)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aMemRefCnt);
|
|
MOZ_ASSERT(aDBRefCnt);
|
|
MOZ_ASSERT(aSliceRefCnt);
|
|
MOZ_ASSERT(aResult);
|
|
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
nsresult rv =
|
|
quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mozilla::MutexAutoLock autolock(mMutex);
|
|
while (mWaiting) {
|
|
mCondVar.Wait();
|
|
}
|
|
|
|
*aMemRefCnt = mMemRefCnt;
|
|
*aDBRefCnt = mDBRefCnt;
|
|
*aSliceRefCnt = mSliceRefCnt;
|
|
*aResult = mResult;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
GetFileReferencesHelper::Run()
|
|
{
|
|
AssertIsOnIOThread();
|
|
|
|
IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get();
|
|
MOZ_ASSERT(mgr);
|
|
|
|
RefPtr<FileManager> fileManager =
|
|
mgr->GetFileManager(mPersistenceType, mOrigin, mDatabaseName);
|
|
|
|
if (fileManager) {
|
|
RefPtr<FileInfo> fileInfo = fileManager->GetFileInfo(mFileId);
|
|
|
|
if (fileInfo) {
|
|
fileInfo->GetReferences(&mMemRefCnt, &mDBRefCnt, &mSliceRefCnt);
|
|
|
|
if (mMemRefCnt != -1) {
|
|
// We added an extra temp ref, so account for that accordingly.
|
|
mMemRefCnt--;
|
|
}
|
|
|
|
mResult = true;
|
|
}
|
|
}
|
|
|
|
mozilla::MutexAutoLock lock(mMutex);
|
|
MOZ_ASSERT(mWaiting);
|
|
|
|
mWaiting = false;
|
|
mCondVar.Notify();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
FlushPendingFileDeletionsRunnable::Run()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<IndexedDatabaseManager> mgr = IndexedDatabaseManager::Get();
|
|
if (NS_WARN_IF(!mgr)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsresult rv = mgr->FlushPendingFileDeletions();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
PermissionRequestHelper::OnPromptComplete(PermissionValue aPermissionValue)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!mActorDestroyed) {
|
|
Unused <<
|
|
PIndexedDBPermissionRequestParent::Send__delete__(this, aPermissionValue);
|
|
}
|
|
}
|
|
|
|
void
|
|
PermissionRequestHelper::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
mActorDestroyed = true;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
|
|
NS_IMPL_ISUPPORTS(DEBUGThreadSlower, nsIThreadObserver)
|
|
|
|
NS_IMETHODIMP
|
|
DEBUGThreadSlower::OnDispatchedEvent(nsIThreadInternal* /* aThread */)
|
|
{
|
|
MOZ_CRASH("Should never be called!");
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DEBUGThreadSlower::OnProcessNextEvent(nsIThreadInternal* /* aThread */,
|
|
bool /* aMayWait */)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DEBUGThreadSlower::AfterProcessNextEvent(nsIThreadInternal* /* aThread */,
|
|
bool /* aEventWasProcessed */)
|
|
{
|
|
MOZ_ASSERT(kDEBUGThreadSleepMS);
|
|
|
|
MOZ_ALWAYS_TRUE(PR_Sleep(PR_MillisecondsToInterval(kDEBUGThreadSleepMS)) ==
|
|
PR_SUCCESS);
|
|
return NS_OK;
|
|
}
|
|
|
|
#endif // DEBUG
|
|
|
|
nsresult
|
|
FileHelper::Init()
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(mFileManager);
|
|
|
|
nsCOMPtr<nsIFile> fileDirectory = mFileManager->GetCheckedDirectory();
|
|
if (NS_WARN_IF(!fileDirectory)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> journalDirectory = mFileManager->EnsureJournalDirectory();
|
|
if (NS_WARN_IF(!journalDirectory)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
DebugOnly<bool> exists;
|
|
MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->Exists(&exists)));
|
|
MOZ_ASSERT(exists);
|
|
|
|
DebugOnly<bool> isDirectory;
|
|
MOZ_ASSERT(NS_SUCCEEDED(journalDirectory->IsDirectory(&isDirectory)));
|
|
MOZ_ASSERT(isDirectory);
|
|
|
|
mFileDirectory = Move(fileDirectory);
|
|
mJournalDirectory= Move(journalDirectory);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<nsIFile>
|
|
FileHelper::GetFile(FileInfo* aFileInfo)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aFileInfo);
|
|
MOZ_ASSERT(mFileManager);
|
|
MOZ_ASSERT(mFileDirectory);
|
|
|
|
const int64_t fileId = aFileInfo->Id();
|
|
MOZ_ASSERT(fileId > 0);
|
|
|
|
nsCOMPtr<nsIFile> file =
|
|
mFileManager->GetFileForId(mFileDirectory, fileId);
|
|
return file.forget();
|
|
}
|
|
|
|
already_AddRefed<nsIFile>
|
|
FileHelper::GetCheckedFile(FileInfo* aFileInfo)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aFileInfo);
|
|
MOZ_ASSERT(mFileManager);
|
|
MOZ_ASSERT(mFileDirectory);
|
|
|
|
const int64_t fileId = aFileInfo->Id();
|
|
MOZ_ASSERT(fileId > 0);
|
|
|
|
nsCOMPtr<nsIFile> file =
|
|
mFileManager->GetCheckedFileForId(mFileDirectory, fileId);
|
|
return file.forget();
|
|
}
|
|
|
|
already_AddRefed<nsIFile>
|
|
FileHelper::GetJournalFile(FileInfo* aFileInfo)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aFileInfo);
|
|
MOZ_ASSERT(mFileManager);
|
|
MOZ_ASSERT(mJournalDirectory);
|
|
|
|
const int64_t fileId = aFileInfo->Id();
|
|
MOZ_ASSERT(fileId > 0);
|
|
|
|
nsCOMPtr<nsIFile> file =
|
|
mFileManager->GetFileForId(mJournalDirectory, fileId);
|
|
return file.forget();
|
|
}
|
|
|
|
nsresult
|
|
FileHelper::CreateFileFromStream(nsIFile* aFile,
|
|
nsIFile* aJournalFile,
|
|
nsIInputStream* aInputStream,
|
|
bool aCompress)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aFile);
|
|
MOZ_ASSERT(aJournalFile);
|
|
MOZ_ASSERT(aInputStream);
|
|
MOZ_ASSERT(mFileManager);
|
|
MOZ_ASSERT(mFileDirectory);
|
|
MOZ_ASSERT(mJournalDirectory);
|
|
|
|
bool exists;
|
|
nsresult rv = aFile->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// DOM blobs that are being stored in IDB are cached by calling
|
|
// IDBDatabase::GetOrCreateFileActorForBlob. So if the same DOM blob is stored
|
|
// again under a different key or in a different object store, we just add
|
|
// a new reference instead of creating a new copy (all such stored blobs share
|
|
// the same id).
|
|
// However, it can happen that CreateFileFromStream failed due to quota
|
|
// exceeded error and for some reason the orphaned file couldn't be deleted
|
|
// immediately. Now, if the operation is being repeated, the DOM blob is
|
|
// already cached, so it has the same file id which clashes with the orphaned
|
|
// file. We could do some tricks to restore previous copy loop, but it's safer
|
|
// to just delete the orphaned file and start from scratch.
|
|
// This corner case is partially simulated in test_file_copy_failure.js
|
|
if (exists) {
|
|
bool isFile;
|
|
rv = aFile->IsFile(&isFile);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!isFile)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
rv = aJournalFile->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!exists)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
rv = aJournalFile->IsFile(&isFile);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!isFile)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
IDB_WARNING("Deleting orphaned file!");
|
|
|
|
rv = RemoveFile(aFile, aJournalFile);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
// Create a journal file first.
|
|
rv = aJournalFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Now try to copy the stream.
|
|
RefPtr<FileOutputStream> fileOutputStream =
|
|
FileOutputStream::Create(mFileManager->Type(),
|
|
mFileManager->Group(),
|
|
mFileManager->Origin(),
|
|
aFile);
|
|
if (NS_WARN_IF(!fileOutputStream)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
if (aCompress) {
|
|
RefPtr<SnappyCompressOutputStream> snappyOutputStream =
|
|
new SnappyCompressOutputStream(fileOutputStream);
|
|
|
|
UniquePtr<char[]> buffer(new char[snappyOutputStream->BlockSize()]);
|
|
|
|
rv = SyncCopy(aInputStream,
|
|
snappyOutputStream,
|
|
buffer.get(),
|
|
snappyOutputStream->BlockSize());
|
|
} else {
|
|
char buffer[kFileCopyBufferSize];
|
|
|
|
rv = SyncCopy(aInputStream,
|
|
fileOutputStream,
|
|
buffer,
|
|
kFileCopyBufferSize);
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
FileHelper::ReplaceFile(nsIFile* aFile,
|
|
nsIFile* aNewFile,
|
|
nsIFile* aNewJournalFile)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aFile);
|
|
MOZ_ASSERT(aNewFile);
|
|
MOZ_ASSERT(aNewJournalFile);
|
|
MOZ_ASSERT(mFileManager);
|
|
MOZ_ASSERT(mFileDirectory);
|
|
MOZ_ASSERT(mJournalDirectory);
|
|
|
|
nsresult rv;
|
|
|
|
int64_t fileSize;
|
|
|
|
if (mFileManager->EnforcingQuota()) {
|
|
rv = aFile->GetFileSize(&fileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
nsAutoString fileName;
|
|
rv = aFile->GetLeafName(fileName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aNewFile->RenameTo(nullptr, fileName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (mFileManager->EnforcingQuota()) {
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
|
|
mFileManager->Group(),
|
|
mFileManager->Origin(),
|
|
fileSize);
|
|
}
|
|
|
|
rv = aNewJournalFile->Remove(false);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
FileHelper::RemoveFile(nsIFile* aFile,
|
|
nsIFile* aJournalFile)
|
|
{
|
|
nsresult rv;
|
|
|
|
int64_t fileSize;
|
|
|
|
if (mFileManager->EnforcingQuota()) {
|
|
rv = aFile->GetFileSize(&fileSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = aFile->Remove(false);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (mFileManager->EnforcingQuota()) {
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
quotaManager->DecreaseUsageForOrigin(mFileManager->Type(),
|
|
mFileManager->Group(),
|
|
mFileManager->Origin(),
|
|
fileSize);
|
|
}
|
|
|
|
rv = aJournalFile->Remove(false);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<FileInfo>
|
|
FileHelper::GetNewFileInfo()
|
|
{
|
|
MOZ_ASSERT(mFileManager);
|
|
|
|
return mFileManager->GetNewFileInfo();
|
|
}
|
|
|
|
nsresult
|
|
FileHelper::SyncCopy(nsIInputStream* aInputStream,
|
|
nsIOutputStream* aOutputStream,
|
|
char* aBuffer,
|
|
uint32_t aBufferSize)
|
|
{
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aInputStream);
|
|
MOZ_ASSERT(aOutputStream);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"FileHelper::SyncCopy",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv;
|
|
|
|
do {
|
|
uint32_t numRead;
|
|
rv = aInputStream->Read(aBuffer, aBufferSize, &numRead);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
break;
|
|
}
|
|
|
|
if (!numRead) {
|
|
break;
|
|
}
|
|
|
|
uint32_t numWrite;
|
|
rv = aOutputStream->Write(aBuffer, numRead, &numWrite);
|
|
if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
|
|
rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
|
|
}
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
break;
|
|
}
|
|
|
|
if (NS_WARN_IF(numWrite != numRead)) {
|
|
rv = NS_ERROR_FAILURE;
|
|
break;
|
|
}
|
|
} while (true);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = aOutputStream->Flush();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
nsresult rv2 = aOutputStream->Close();
|
|
if (NS_WARN_IF(NS_FAILED(rv2))) {
|
|
return NS_SUCCEEDED(rv) ? rv2 : rv;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
} // namespace indexedDB
|
|
} // namespace dom
|
|
} // namespace mozilla
|
|
|
|
#undef IDB_MOBILE
|
|
#undef IDB_DEBUG_LOG
|
|
#undef ASSERT_UNLESS_FUZZING
|
|
#undef DISABLE_ASSERTS_FOR_FUZZING
|