mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-08 08:17:31 +09:00
Normally we wouldn't need to send any notifications if the actor was already destroyed, but it can be a VersionChangeOp which needs to notify its parent operation (OpenDatabaseOp) about the failure. So SendFailureResult needs to be called even when the actor was destroyed. Normal operations redundantly check if the actor was destroyed in SendSuccessResult and SendFailureResult, therefore it's ok to call it in all cases here.
29652 lines
783 KiB
C++
29652 lines
783 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 <stdint.h> // UINTPTR_MAX, uintptr_t
|
|
#include "FileInfo.h"
|
|
#include "FileManager.h"
|
|
#include "IDBObjectStore.h"
|
|
#include "IDBTransaction.h"
|
|
#include "IndexedDatabase.h"
|
|
#include "IndexedDatabaseInlines.h"
|
|
#include "IndexedDatabaseManager.h"
|
|
#include "js/StructuredClone.h"
|
|
#include "js/Value.h"
|
|
#include "jsapi.h"
|
|
#include "KeyPath.h"
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/AppProcessChecker.h"
|
|
#include "mozilla/AutoRestore.h"
|
|
#include "mozilla/Casting.h"
|
|
#include "mozilla/CheckedInt.h"
|
|
#include "mozilla/EndianUtils.h"
|
|
#include "mozilla/ErrorNames.h"
|
|
#include "mozilla/LazyIdleThread.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/SnappyCompressOutputStream.h"
|
|
#include "mozilla/SnappyUncompressInputStream.h"
|
|
#include "mozilla/StaticPtr.h"
|
|
#include "mozilla/storage.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "mozilla/UniquePtrExtensions.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/dom/File.h"
|
|
#include "mozilla/dom/StructuredCloneTags.h"
|
|
#include "mozilla/dom/TabParent.h"
|
|
#include "mozilla/dom/filehandle/ActorsParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseRequestParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBTransactionParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIDBVersionChangeTransactionParent.h"
|
|
#include "mozilla/dom/indexedDB/PBackgroundIndexedDBUtilsParent.h"
|
|
#include "mozilla/dom/indexedDB/PIndexedDBPermissionRequestParent.h"
|
|
#include "mozilla/dom/ipc/BlobParent.h"
|
|
#include "mozilla/dom/quota/Client.h"
|
|
#include "mozilla/dom/quota/FileStreams.h"
|
|
#include "mozilla/dom/quota/OriginScope.h"
|
|
#include "mozilla/dom/quota/QuotaManager.h"
|
|
#include "mozilla/dom/quota/UsageInfo.h"
|
|
#include "mozilla/ipc/BackgroundParent.h"
|
|
#include "mozilla/ipc/BackgroundUtils.h"
|
|
#include "mozilla/ipc/InputStreamParams.h"
|
|
#include "mozilla/ipc/InputStreamUtils.h"
|
|
#include "mozilla/ipc/PBackground.h"
|
|
#include "mozilla/Scoped.h"
|
|
#include "mozilla/storage/Variant.h"
|
|
#include "nsAutoPtr.h"
|
|
#include "nsCharSeparatedTokenizer.h"
|
|
#include "nsClassHashtable.h"
|
|
#include "nsCOMPtr.h"
|
|
#include "nsDataHashtable.h"
|
|
#include "nsEscape.h"
|
|
#include "nsHashKeys.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsISimpleEnumerator.h"
|
|
#include "nsIAppsService.h"
|
|
#include "nsIEventTarget.h"
|
|
#include "nsIFile.h"
|
|
#include "nsIFileURL.h"
|
|
#include "nsIFileProtocolHandler.h"
|
|
#include "nsIInputStream.h"
|
|
#include "nsIInterfaceRequestor.h"
|
|
#include "nsInterfaceHashtable.h"
|
|
#include "nsIOutputStream.h"
|
|
#include "nsIPipe.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsISupports.h"
|
|
#include "nsISupportsImpl.h"
|
|
#include "nsISupportsPriority.h"
|
|
#include "nsIThread.h"
|
|
#include "nsITimer.h"
|
|
#include "nsIURI.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsQueryObject.h"
|
|
#include "nsRefPtrHashtable.h"
|
|
#include "nsStreamUtils.h"
|
|
#include "nsString.h"
|
|
#include "nsStringStream.h"
|
|
#include "nsThreadPool.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsXPCOMCID.h"
|
|
#include "PermissionRequestBase.h"
|
|
#include "ProfilerHelpers.h"
|
|
#include "prsystem.h"
|
|
#include "prtime.h"
|
|
#include "ReportInternalError.h"
|
|
#include "snappy/snappy.h"
|
|
|
|
#define DISABLE_ASSERTS_FOR_FUZZING 0
|
|
|
|
#if DISABLE_ASSERTS_FOR_FUZZING
|
|
#define ASSERT_UNLESS_FUZZING(...) do { } while (0)
|
|
#else
|
|
#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
|
|
#endif
|
|
|
|
#define IDB_DEBUG_LOG(_args) \
|
|
MOZ_LOG(IndexedDatabaseManager::GetLoggingModule(), \
|
|
LogLevel::Debug, \
|
|
_args )
|
|
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
#define IDB_MOBILE
|
|
#endif
|
|
|
|
#define BLOB_IMPL_STORED_FILE_IID \
|
|
{0x6b505c84, 0x2c60, 0x4ffb, {0x8b, 0x91, 0xfe, 0x22, 0xb1, 0xec, 0x75, 0xe2}}
|
|
|
|
namespace mozilla {
|
|
|
|
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc,
|
|
PRFileDesc,
|
|
PR_Close);
|
|
|
|
namespace dom {
|
|
namespace indexedDB {
|
|
|
|
using namespace mozilla::dom::quota;
|
|
using namespace mozilla::ipc;
|
|
|
|
namespace {
|
|
|
|
class ConnectionPool;
|
|
class Cursor;
|
|
class Database;
|
|
struct DatabaseActorInfo;
|
|
class DatabaseFile;
|
|
class DatabaseLoggingInfo;
|
|
class DatabaseMaintenance;
|
|
class Factory;
|
|
class Maintenance;
|
|
class MutableFile;
|
|
class OpenDatabaseOp;
|
|
class TransactionBase;
|
|
class TransactionDatabaseOperationBase;
|
|
class VersionChangeTransaction;
|
|
|
|
/*******************************************************************************
|
|
* Constants
|
|
******************************************************************************/
|
|
|
|
// If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major
|
|
// schema version.
|
|
static_assert(JS_STRUCTURED_CLONE_VERSION == 8,
|
|
"Need to update the major schema version.");
|
|
|
|
// Major schema version. Bump for almost everything.
|
|
const uint32_t kMajorSchemaVersion = 25;
|
|
|
|
// Minor schema version. Should almost always be 0 (maybe bump on release
|
|
// branches if we have to).
|
|
const uint32_t kMinorSchemaVersion = 0;
|
|
|
|
// The schema version we store in the SQLite database is a (signed) 32-bit
|
|
// integer. The major version is left-shifted 4 bits so the max value is
|
|
// 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
|
|
static_assert(kMajorSchemaVersion <= 0xFFFFFFF,
|
|
"Major version needs to fit in 28 bits.");
|
|
static_assert(kMinorSchemaVersion <= 0xF,
|
|
"Minor version needs to fit in 4 bits.");
|
|
|
|
const int32_t kSQLiteSchemaVersion =
|
|
int32_t((kMajorSchemaVersion << 4) + kMinorSchemaVersion);
|
|
|
|
const int32_t kStorageProgressGranularity = 1000;
|
|
|
|
// Changing the value here will override the page size of new databases only.
|
|
// A journal mode change and VACUUM are needed to change existing databases, so
|
|
// the best way to do that is to use the schema version upgrade mechanism.
|
|
const uint32_t kSQLitePageSizeOverride =
|
|
#ifdef IDB_MOBILE
|
|
2048;
|
|
#else
|
|
4096;
|
|
#endif
|
|
|
|
static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 ||
|
|
(kSQLitePageSizeOverride % 2 == 0 &&
|
|
kSQLitePageSizeOverride >= 512 &&
|
|
kSQLitePageSizeOverride <= 65536),
|
|
"Must be 0 (disabled) or a power of 2 between 512 and 65536!");
|
|
|
|
// Set to -1 to use SQLite's default, 0 to disable, or some positive number to
|
|
// enforce a custom limit.
|
|
const int32_t kMaxWALPages = 5000; // 20MB on desktop, 10MB on mobile.
|
|
|
|
// Set to some multiple of the page size to grow the database in larger chunks.
|
|
const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2;
|
|
|
|
static_assert(kSQLiteGrowthIncrement >= 0 &&
|
|
kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
|
|
kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
|
|
"Must be 0 (disabled) or a positive multiple of the page size!");
|
|
|
|
// The maximum number of threads that can be used for database activity at a
|
|
// single time.
|
|
const uint32_t kMaxConnectionThreadCount = 20;
|
|
|
|
static_assert(kMaxConnectionThreadCount, "Must have at least one thread!");
|
|
|
|
// The maximum number of threads to keep when idle. Threads that become idle in
|
|
// excess of this number will be shut down immediately.
|
|
const uint32_t kMaxIdleConnectionThreadCount = 2;
|
|
|
|
static_assert(kMaxConnectionThreadCount >= kMaxIdleConnectionThreadCount,
|
|
"Idle thread limit must be less than total thread limit!");
|
|
|
|
// The length of time that database connections will be held open after all
|
|
// transactions have completed before doing idle maintenance.
|
|
const uint32_t kConnectionIdleMaintenanceMS = 2 * 1000; // 2 seconds
|
|
|
|
// The length of time that database connections will be held open after all
|
|
// transactions and maintenance have completed.
|
|
const uint32_t kConnectionIdleCloseMS = 10 * 1000; // 10 seconds
|
|
|
|
// The length of time that idle threads will stay alive before being shut down.
|
|
const uint32_t kConnectionThreadIdleMS = 30 * 1000; // 30 seconds
|
|
|
|
#define SAVEPOINT_CLAUSE "SAVEPOINT sp;"
|
|
|
|
const uint32_t kFileCopyBufferSize = 32768;
|
|
|
|
#define JOURNAL_DIRECTORY_NAME "journals"
|
|
|
|
const char kFileManagerDirectoryNameSuffix[] = ".files";
|
|
const char kSQLiteJournalSuffix[] = ".sqlite-journal";
|
|
const char kSQLiteSHMSuffix[] = ".sqlite-shm";
|
|
const char kSQLiteWALSuffix[] = ".sqlite-wal";
|
|
|
|
const char kPrefFileHandleEnabled[] = "dom.fileHandle.enabled";
|
|
|
|
#define IDB_PREFIX "indexedDB"
|
|
|
|
#define PERMISSION_STRING_CHROME_BASE IDB_PREFIX "-chrome-"
|
|
#define PERMISSION_STRING_CHROME_READ_SUFFIX "-read"
|
|
#define PERMISSION_STRING_CHROME_WRITE_SUFFIX "-write"
|
|
|
|
#ifdef DEBUG
|
|
|
|
const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
|
|
const uint32_t kDEBUGThreadSleepMS = 0;
|
|
|
|
const int32_t kDEBUGTransactionThreadPriority =
|
|
nsISupportsPriority::PRIORITY_NORMAL;
|
|
const uint32_t kDEBUGTransactionThreadSleepMS = 0;
|
|
|
|
#endif
|
|
|
|
template <size_t N>
|
|
constexpr size_t
|
|
LiteralStringLength(const char (&aArr)[N])
|
|
{
|
|
static_assert(N, "Zero-length string literal?!");
|
|
|
|
// Don't include the null terminator.
|
|
return N - 1;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Metadata classes
|
|
******************************************************************************/
|
|
|
|
struct FullIndexMetadata
|
|
{
|
|
IndexMetadata mCommonMetadata;
|
|
|
|
bool mDeleted;
|
|
|
|
public:
|
|
FullIndexMetadata()
|
|
: mCommonMetadata(0, nsString(), KeyPath(0), nsCString(), false, false, false)
|
|
, mDeleted(false)
|
|
{
|
|
// This can happen either on the QuotaManager IO thread or on a
|
|
// versionchange transaction thread. These threads can never race so this is
|
|
// totally safe.
|
|
}
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullIndexMetadata)
|
|
|
|
private:
|
|
~FullIndexMetadata()
|
|
{ }
|
|
};
|
|
|
|
typedef nsRefPtrHashtable<nsUint64HashKey, FullIndexMetadata> IndexTable;
|
|
|
|
struct FullObjectStoreMetadata
|
|
{
|
|
ObjectStoreMetadata mCommonMetadata;
|
|
IndexTable mIndexes;
|
|
|
|
// These two members are only ever touched on a transaction thread!
|
|
int64_t mNextAutoIncrementId;
|
|
int64_t mCommittedAutoIncrementId;
|
|
|
|
bool mDeleted;
|
|
|
|
public:
|
|
FullObjectStoreMetadata()
|
|
: mCommonMetadata(0, nsString(), KeyPath(0), false)
|
|
, mNextAutoIncrementId(0)
|
|
, mCommittedAutoIncrementId(0)
|
|
, mDeleted(false)
|
|
{
|
|
// This can happen either on the QuotaManager IO thread or on a
|
|
// versionchange transaction thread. These threads can never race so this is
|
|
// totally safe.
|
|
}
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);
|
|
|
|
bool
|
|
HasLiveIndexes() const;
|
|
|
|
private:
|
|
~FullObjectStoreMetadata()
|
|
{ }
|
|
};
|
|
|
|
typedef nsRefPtrHashtable<nsUint64HashKey, FullObjectStoreMetadata>
|
|
ObjectStoreTable;
|
|
|
|
struct FullDatabaseMetadata
|
|
{
|
|
DatabaseMetadata mCommonMetadata;
|
|
nsCString mDatabaseId;
|
|
nsString mFilePath;
|
|
ObjectStoreTable mObjectStores;
|
|
|
|
int64_t mNextObjectStoreId;
|
|
int64_t mNextIndexId;
|
|
|
|
public:
|
|
explicit FullDatabaseMetadata(const DatabaseMetadata& aCommonMetadata)
|
|
: mCommonMetadata(aCommonMetadata)
|
|
, mNextObjectStoreId(0)
|
|
, mNextIndexId(0)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
}
|
|
|
|
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullDatabaseMetadata)
|
|
|
|
already_AddRefed<FullDatabaseMetadata>
|
|
Duplicate() const;
|
|
|
|
private:
|
|
~FullDatabaseMetadata()
|
|
{ }
|
|
};
|
|
|
|
template <class MetadataType>
|
|
class MOZ_STACK_CLASS MetadataNameOrIdMatcher final
|
|
{
|
|
typedef MetadataNameOrIdMatcher<MetadataType> SelfType;
|
|
|
|
const int64_t mId;
|
|
const nsString mName;
|
|
RefPtr<MetadataType> mMetadata;
|
|
bool mCheckName;
|
|
|
|
public:
|
|
template <class Enumerable>
|
|
static MetadataType*
|
|
Match(const Enumerable& aEnumerable, uint64_t aId, const nsAString& aName)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aId);
|
|
|
|
SelfType closure(aId, aName);
|
|
MatchHelper(aEnumerable, &closure);
|
|
|
|
return closure.mMetadata;
|
|
}
|
|
|
|
template <class Enumerable>
|
|
static MetadataType*
|
|
Match(const Enumerable& aEnumerable, uint64_t aId)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aId);
|
|
|
|
SelfType closure(aId);
|
|
MatchHelper(aEnumerable, &closure);
|
|
|
|
return closure.mMetadata;
|
|
}
|
|
|
|
private:
|
|
MetadataNameOrIdMatcher(const int64_t& aId, const nsAString& aName)
|
|
: mId(aId)
|
|
, mName(PromiseFlatString(aName))
|
|
, mMetadata(nullptr)
|
|
, mCheckName(true)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aId);
|
|
}
|
|
|
|
explicit MetadataNameOrIdMatcher(const int64_t& aId)
|
|
: mId(aId)
|
|
, mMetadata(nullptr)
|
|
, mCheckName(false)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aId);
|
|
}
|
|
|
|
template <class Enumerable>
|
|
static void
|
|
MatchHelper(const Enumerable& aEnumerable, SelfType* aClosure)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aClosure);
|
|
|
|
for (auto iter = aEnumerable.ConstIter(); !iter.Done(); iter.Next()) {
|
|
#ifdef DEBUG
|
|
const uint64_t key = iter.Key();
|
|
#endif
|
|
MetadataType* value = iter.UserData();
|
|
MOZ_ASSERT(key != 0);
|
|
MOZ_ASSERT(value);
|
|
|
|
if (!value->mDeleted &&
|
|
(aClosure->mId == value->mCommonMetadata.id() ||
|
|
(aClosure->mCheckName &&
|
|
aClosure->mName == value->mCommonMetadata.name()))) {
|
|
aClosure->mMetadata = value;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
struct IndexDataValue final
|
|
{
|
|
int64_t mIndexId;
|
|
Key mKey;
|
|
Key mSortKey;
|
|
bool mUnique;
|
|
|
|
IndexDataValue()
|
|
: mIndexId(0)
|
|
, mUnique(false)
|
|
{
|
|
MOZ_COUNT_CTOR(IndexDataValue);
|
|
}
|
|
|
|
explicit
|
|
IndexDataValue(const IndexDataValue& aOther)
|
|
: mIndexId(aOther.mIndexId)
|
|
, mKey(aOther.mKey)
|
|
, mSortKey(aOther.mSortKey)
|
|
, mUnique(aOther.mUnique)
|
|
{
|
|
MOZ_ASSERT(!aOther.mKey.IsUnset());
|
|
|
|
MOZ_COUNT_CTOR(IndexDataValue);
|
|
}
|
|
|
|
IndexDataValue(int64_t aIndexId, bool aUnique, const Key& aKey)
|
|
: mIndexId(aIndexId)
|
|
, mKey(aKey)
|
|
, mUnique(aUnique)
|
|
{
|
|
MOZ_ASSERT(!aKey.IsUnset());
|
|
|
|
MOZ_COUNT_CTOR(IndexDataValue);
|
|
}
|
|
|
|
IndexDataValue(int64_t aIndexId, bool aUnique, const Key& aKey,
|
|
const Key& aSortKey)
|
|
: mIndexId(aIndexId)
|
|
, mKey(aKey)
|
|
, mSortKey(aSortKey)
|
|
, mUnique(aUnique)
|
|
{
|
|
MOZ_ASSERT(!aKey.IsUnset());
|
|
|
|
MOZ_COUNT_CTOR(IndexDataValue);
|
|
}
|
|
|
|
~IndexDataValue()
|
|
{
|
|
MOZ_COUNT_DTOR(IndexDataValue);
|
|
}
|
|
|
|
bool
|
|
operator==(const IndexDataValue& aOther) const
|
|
{
|
|
if (mIndexId != aOther.mIndexId) {
|
|
return false;
|
|
}
|
|
if (mSortKey.IsUnset()) {
|
|
return mKey == aOther.mKey;
|
|
}
|
|
return mSortKey == aOther.mSortKey;
|
|
}
|
|
|
|
bool
|
|
operator<(const IndexDataValue& aOther) const
|
|
{
|
|
if (mIndexId == aOther.mIndexId) {
|
|
if (mSortKey.IsUnset()) {
|
|
return mKey < aOther.mKey;
|
|
}
|
|
return mSortKey < aOther.mSortKey;
|
|
}
|
|
|
|
return mIndexId < aOther.mIndexId;
|
|
}
|
|
};
|
|
|
|
/*******************************************************************************
|
|
* SQLite functions
|
|
******************************************************************************/
|
|
|
|
int32_t
|
|
MakeSchemaVersion(uint32_t aMajorSchemaVersion,
|
|
uint32_t aMinorSchemaVersion)
|
|
{
|
|
return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion);
|
|
}
|
|
|
|
uint32_t
|
|
HashName(const nsAString& aName)
|
|
{
|
|
struct Helper
|
|
{
|
|
static uint32_t
|
|
RotateBitsLeft32(uint32_t aValue, uint8_t aBits)
|
|
{
|
|
MOZ_ASSERT(aBits < 32);
|
|
return (aValue << aBits) | (aValue >> (32 - aBits));
|
|
}
|
|
};
|
|
|
|
static const uint32_t kGoldenRatioU32 = 0x9e3779b9u;
|
|
|
|
const char16_t* str = aName.BeginReading();
|
|
size_t length = aName.Length();
|
|
|
|
uint32_t hash = 0;
|
|
for (size_t i = 0; i < length; i++) {
|
|
hash = kGoldenRatioU32 * (Helper::RotateBitsLeft32(hash, 5) ^ str[i]);
|
|
}
|
|
|
|
return hash;
|
|
}
|
|
|
|
nsresult
|
|
ClampResultCode(nsresult aResultCode)
|
|
{
|
|
if (NS_SUCCEEDED(aResultCode) ||
|
|
NS_ERROR_GET_MODULE(aResultCode) == NS_ERROR_MODULE_DOM_INDEXEDDB) {
|
|
return aResultCode;
|
|
}
|
|
|
|
switch (aResultCode) {
|
|
case NS_ERROR_FILE_NO_DEVICE_SPACE:
|
|
return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
|
|
case NS_ERROR_STORAGE_CONSTRAINT:
|
|
return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR;
|
|
default:
|
|
#ifdef DEBUG
|
|
nsPrintfCString message("Converting non-IndexedDB error code (0x%X) to "
|
|
"NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR",
|
|
aResultCode);
|
|
NS_WARNING(message.get());
|
|
#else
|
|
;
|
|
#endif
|
|
}
|
|
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
void
|
|
GetDatabaseFilename(const nsAString& aName,
|
|
nsAutoString& aDatabaseFilename)
|
|
{
|
|
MOZ_ASSERT(aDatabaseFilename.IsEmpty());
|
|
|
|
aDatabaseFilename.AppendInt(HashName(aName));
|
|
|
|
nsCString escapedName;
|
|
if (!NS_Escape(NS_ConvertUTF16toUTF8(aName), escapedName, url_XPAlphas)) {
|
|
MOZ_CRASH("Can't escape database name!");
|
|
}
|
|
|
|
const char* forwardIter = escapedName.BeginReading();
|
|
const char* backwardIter = escapedName.EndReading() - 1;
|
|
|
|
nsAutoCString substring;
|
|
while (forwardIter <= backwardIter && substring.Length() < 21) {
|
|
if (substring.Length() % 2) {
|
|
substring.Append(*backwardIter--);
|
|
} else {
|
|
substring.Append(*forwardIter++);
|
|
}
|
|
}
|
|
|
|
aDatabaseFilename.AppendASCII(substring.get(), substring.Length());
|
|
}
|
|
|
|
uint32_t
|
|
CompressedByteCountForNumber(uint64_t aNumber)
|
|
{
|
|
// All bytes have 7 bits available.
|
|
uint32_t count = 1;
|
|
while ((aNumber >>= 7)) {
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
uint32_t
|
|
CompressedByteCountForIndexId(int64_t aIndexId)
|
|
{
|
|
MOZ_ASSERT(aIndexId);
|
|
MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
|
|
"Overflow!");
|
|
|
|
return CompressedByteCountForNumber(uint64_t(aIndexId * 2));
|
|
}
|
|
|
|
void
|
|
WriteCompressedNumber(uint64_t aNumber, uint8_t** aIterator)
|
|
{
|
|
MOZ_ASSERT(aIterator);
|
|
MOZ_ASSERT(*aIterator);
|
|
|
|
uint8_t*& buffer = *aIterator;
|
|
|
|
#ifdef DEBUG
|
|
const uint8_t* bufferStart = buffer;
|
|
const uint64_t originalNumber = aNumber;
|
|
#endif
|
|
|
|
while (true) {
|
|
uint64_t shiftedNumber = aNumber >> 7;
|
|
if (shiftedNumber) {
|
|
*buffer++ = uint8_t(0x80 | (aNumber & 0x7f));
|
|
aNumber = shiftedNumber;
|
|
} else {
|
|
*buffer++ = uint8_t(aNumber);
|
|
break;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(buffer > bufferStart);
|
|
MOZ_ASSERT(uint32_t(buffer - bufferStart) ==
|
|
CompressedByteCountForNumber(originalNumber));
|
|
}
|
|
|
|
uint64_t
|
|
ReadCompressedNumber(const uint8_t** aIterator, const uint8_t* aEnd)
|
|
{
|
|
MOZ_ASSERT(aIterator);
|
|
MOZ_ASSERT(*aIterator);
|
|
MOZ_ASSERT(aEnd);
|
|
MOZ_ASSERT(*aIterator < aEnd);
|
|
|
|
const uint8_t*& buffer = *aIterator;
|
|
|
|
uint8_t shiftCounter = 0;
|
|
uint64_t result = 0;
|
|
|
|
while (true) {
|
|
MOZ_ASSERT(shiftCounter <= 56, "Shifted too many bits!");
|
|
|
|
result += (uint64_t(*buffer & 0x7f) << shiftCounter);
|
|
shiftCounter += 7;
|
|
|
|
if (!(*buffer++ & 0x80)) {
|
|
break;
|
|
}
|
|
|
|
if (NS_WARN_IF(buffer == aEnd)) {
|
|
MOZ_ASSERT(false);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void
|
|
WriteCompressedIndexId(int64_t aIndexId, bool aUnique, uint8_t** aIterator)
|
|
{
|
|
MOZ_ASSERT(aIndexId);
|
|
MOZ_ASSERT(UINT64_MAX - uint64_t(aIndexId) >= uint64_t(aIndexId),
|
|
"Overflow!");
|
|
MOZ_ASSERT(aIterator);
|
|
MOZ_ASSERT(*aIterator);
|
|
|
|
const uint64_t indexId = (uint64_t(aIndexId * 2) | (aUnique ? 1 : 0));
|
|
WriteCompressedNumber(indexId, aIterator);
|
|
}
|
|
|
|
void
|
|
ReadCompressedIndexId(const uint8_t** aIterator,
|
|
const uint8_t* aEnd,
|
|
int64_t* aIndexId,
|
|
bool* aUnique)
|
|
{
|
|
MOZ_ASSERT(aIterator);
|
|
MOZ_ASSERT(*aIterator);
|
|
MOZ_ASSERT(aIndexId);
|
|
MOZ_ASSERT(aUnique);
|
|
|
|
uint64_t indexId = ReadCompressedNumber(aIterator, aEnd);
|
|
|
|
if (indexId % 2) {
|
|
*aUnique = true;
|
|
indexId--;
|
|
} else {
|
|
*aUnique = false;
|
|
}
|
|
|
|
MOZ_ASSERT(UINT64_MAX / 2 >= uint64_t(indexId), "Bad index id!");
|
|
|
|
*aIndexId = int64_t(indexId / 2);
|
|
}
|
|
|
|
// static
|
|
nsresult
|
|
MakeCompressedIndexDataValues(
|
|
const FallibleTArray<IndexDataValue>& aIndexValues,
|
|
UniqueFreePtr<uint8_t>& aCompressedIndexDataValues,
|
|
uint32_t* aCompressedIndexDataValuesLength)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(!aCompressedIndexDataValues);
|
|
MOZ_ASSERT(aCompressedIndexDataValuesLength);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"MakeCompressedIndexDataValues",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
const uint32_t arrayLength = aIndexValues.Length();
|
|
if (!arrayLength) {
|
|
*aCompressedIndexDataValuesLength = 0;
|
|
return NS_OK;
|
|
}
|
|
|
|
// First calculate the size of the final buffer.
|
|
uint32_t blobDataLength = 0;
|
|
|
|
for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
|
|
const IndexDataValue& info = aIndexValues[arrayIndex];
|
|
const nsCString& keyBuffer = info.mKey.GetBuffer();
|
|
const nsCString& sortKeyBuffer = info.mSortKey.GetBuffer();
|
|
const uint32_t keyBufferLength = keyBuffer.Length();
|
|
const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
|
|
|
|
MOZ_ASSERT(!keyBuffer.IsEmpty());
|
|
|
|
const CheckedUint32 infoLength =
|
|
CheckedUint32(CompressedByteCountForIndexId(info.mIndexId)) +
|
|
CompressedByteCountForNumber(keyBufferLength) +
|
|
CompressedByteCountForNumber(sortKeyBufferLength) +
|
|
keyBufferLength +
|
|
sortKeyBufferLength;
|
|
// Don't let |infoLength| overflow.
|
|
if (NS_WARN_IF(!infoLength.isValid())) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
// Don't let |blobDataLength| overflow.
|
|
if (NS_WARN_IF(UINT32_MAX - infoLength.value() < blobDataLength)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
|
|
blobDataLength += infoLength.value();
|
|
}
|
|
|
|
UniqueFreePtr<uint8_t> blobData(
|
|
static_cast<uint8_t*>(malloc(blobDataLength)));
|
|
if (NS_WARN_IF(!blobData)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
uint8_t* blobDataIter = blobData.get();
|
|
|
|
for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
|
|
const IndexDataValue& info = aIndexValues[arrayIndex];
|
|
const nsCString& keyBuffer = info.mKey.GetBuffer();
|
|
const nsCString& sortKeyBuffer = info.mSortKey.GetBuffer();
|
|
const uint32_t keyBufferLength = keyBuffer.Length();
|
|
const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();
|
|
|
|
WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter);
|
|
WriteCompressedNumber(keyBufferLength, &blobDataIter);
|
|
|
|
memcpy(blobDataIter, keyBuffer.get(), keyBufferLength);
|
|
blobDataIter += keyBufferLength;
|
|
|
|
WriteCompressedNumber(sortKeyBufferLength, &blobDataIter);
|
|
|
|
memcpy(blobDataIter, sortKeyBuffer.get(), sortKeyBufferLength);
|
|
blobDataIter += sortKeyBufferLength;
|
|
}
|
|
|
|
MOZ_ASSERT(blobDataIter == blobData.get() + blobDataLength);
|
|
|
|
aCompressedIndexDataValues.swap(blobData);
|
|
*aCompressedIndexDataValuesLength = uint32_t(blobDataLength);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
ReadCompressedIndexDataValuesFromBlob(const uint8_t* aBlobData,
|
|
uint32_t aBlobDataLength,
|
|
nsTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aBlobData);
|
|
MOZ_ASSERT(aBlobDataLength);
|
|
MOZ_ASSERT(aIndexValues.IsEmpty());
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"ReadCompressedIndexDataValuesFromBlob",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
if (uintptr_t(aBlobData) > UINTPTR_MAX - aBlobDataLength) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
const uint8_t* blobDataIter = aBlobData;
|
|
const uint8_t* blobDataEnd = aBlobData + aBlobDataLength;
|
|
|
|
while (blobDataIter < blobDataEnd) {
|
|
int64_t indexId;
|
|
bool unique;
|
|
ReadCompressedIndexId(&blobDataIter, blobDataEnd, &indexId, &unique);
|
|
|
|
if (NS_WARN_IF(blobDataIter == blobDataEnd)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
// Read key buffer length.
|
|
const uint64_t keyBufferLength =
|
|
ReadCompressedNumber(&blobDataIter, blobDataEnd);
|
|
|
|
if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
|
|
NS_WARN_IF(keyBufferLength > uint64_t(UINT32_MAX)) ||
|
|
NS_WARN_IF(keyBufferLength > uintptr_t(blobDataEnd)) ||
|
|
NS_WARN_IF(blobDataIter > blobDataEnd - keyBufferLength)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
nsCString keyBuffer(reinterpret_cast<const char*>(blobDataIter),
|
|
uint32_t(keyBufferLength));
|
|
blobDataIter += keyBufferLength;
|
|
|
|
IndexDataValue idv(indexId, unique, Key(keyBuffer));
|
|
|
|
// Read sort key buffer length.
|
|
const uint64_t sortKeyBufferLength =
|
|
ReadCompressedNumber(&blobDataIter, blobDataEnd);
|
|
|
|
if (sortKeyBufferLength > 0) {
|
|
if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
|
|
NS_WARN_IF(sortKeyBufferLength > uint64_t(UINT32_MAX)) ||
|
|
NS_WARN_IF(sortKeyBufferLength > uintptr_t(blobDataEnd)) ||
|
|
NS_WARN_IF(blobDataIter > blobDataEnd - sortKeyBufferLength)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
nsCString sortKeyBuffer(reinterpret_cast<const char*>(blobDataIter),
|
|
uint32_t(sortKeyBufferLength));
|
|
blobDataIter += sortKeyBufferLength;
|
|
|
|
idv.mSortKey = Key(sortKeyBuffer);
|
|
}
|
|
|
|
if (NS_WARN_IF(!aIndexValues.InsertElementSorted(idv, fallible))) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(blobDataIter == blobDataEnd);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// static
|
|
template <typename T>
|
|
nsresult
|
|
ReadCompressedIndexDataValuesFromSource(T* aSource,
|
|
uint32_t aColumnIndex,
|
|
nsTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aSource);
|
|
MOZ_ASSERT(aIndexValues.IsEmpty());
|
|
|
|
int32_t columnType;
|
|
nsresult rv = aSource->GetTypeOfIndex(aColumnIndex, &columnType);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (columnType == mozIStorageStatement::VALUE_TYPE_NULL) {
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(columnType == mozIStorageStatement::VALUE_TYPE_BLOB);
|
|
|
|
const uint8_t* blobData;
|
|
uint32_t blobDataLength;
|
|
rv = aSource->GetSharedBlob(aColumnIndex, &blobDataLength, &blobData);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!blobDataLength)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
return NS_ERROR_FILE_CORRUPTED;
|
|
}
|
|
|
|
rv = ReadCompressedIndexDataValuesFromBlob(blobData,
|
|
blobDataLength,
|
|
aIndexValues);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
ReadCompressedIndexDataValues(mozIStorageStatement* aStatement,
|
|
uint32_t aColumnIndex,
|
|
nsTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
return ReadCompressedIndexDataValuesFromSource(aStatement,
|
|
aColumnIndex,
|
|
aIndexValues);
|
|
}
|
|
|
|
nsresult
|
|
ReadCompressedIndexDataValues(mozIStorageValueArray* aValues,
|
|
uint32_t aColumnIndex,
|
|
nsTArray<IndexDataValue>& aIndexValues)
|
|
{
|
|
return ReadCompressedIndexDataValuesFromSource(aValues,
|
|
aColumnIndex,
|
|
aIndexValues);
|
|
}
|
|
|
|
nsresult
|
|
CreateFileTables(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CreateFileTables",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// Table `file`
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE file ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"refcount INTEGER NOT NULL"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_insert_trigger "
|
|
"AFTER INSERT ON object_data "
|
|
"FOR EACH ROW "
|
|
"WHEN NEW.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(NULL, NEW.file_ids); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_update_trigger "
|
|
"AFTER UPDATE OF file_ids ON object_data "
|
|
"FOR EACH ROW "
|
|
"WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_delete_trigger "
|
|
"AFTER DELETE ON object_data "
|
|
"FOR EACH ROW WHEN OLD.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(OLD.file_ids, NULL); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER file_update_trigger "
|
|
"AFTER UPDATE ON file "
|
|
"FOR EACH ROW WHEN NEW.refcount = 0 "
|
|
"BEGIN "
|
|
"DELETE FROM file WHERE id = OLD.id; "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
CreateTables(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CreateTables",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// Table `database`
|
|
|
|
// There are two reasons for having the origin column.
|
|
// First, we can ensure that we don't have collisions in the origin hash we
|
|
// use for the path because when we open the db we can make sure that the
|
|
// origins exactly match. Second, chrome code crawling through the idb
|
|
// directory can figure out the origin of every db without having to
|
|
// reverse-engineer our hash scheme.
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE database"
|
|
"( name TEXT PRIMARY KEY"
|
|
", origin TEXT NOT NULL"
|
|
", version INTEGER NOT NULL DEFAULT 0"
|
|
", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
|
|
", last_analyze_time INTEGER NOT NULL DEFAULT 0"
|
|
", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
|
|
") WITHOUT ROWID;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Table `object_store`
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_store"
|
|
"( id INTEGER PRIMARY KEY"
|
|
", auto_increment INTEGER NOT NULL DEFAULT 0"
|
|
", name TEXT NOT NULL"
|
|
", key_path TEXT"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Table `object_store_index`
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_store_index"
|
|
"( id INTEGER PRIMARY KEY"
|
|
", object_store_id INTEGER NOT NULL"
|
|
", name TEXT NOT NULL"
|
|
", key_path TEXT NOT NULL"
|
|
", unique_index INTEGER NOT NULL"
|
|
", multientry INTEGER NOT NULL"
|
|
", locale TEXT"
|
|
", is_auto_locale BOOLEAN NOT NULL"
|
|
", FOREIGN KEY (object_store_id) "
|
|
"REFERENCES object_store(id) "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Table `object_data`
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_data"
|
|
"( object_store_id INTEGER NOT NULL"
|
|
", key BLOB NOT NULL"
|
|
", index_data_values BLOB DEFAULT NULL"
|
|
", file_ids TEXT"
|
|
", data BLOB NOT NULL"
|
|
", PRIMARY KEY (object_store_id, key)"
|
|
", FOREIGN KEY (object_store_id) "
|
|
"REFERENCES object_store(id) "
|
|
") WITHOUT ROWID;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Table `index_data`
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE index_data"
|
|
"( index_id INTEGER NOT NULL"
|
|
", value BLOB NOT NULL"
|
|
", object_data_key BLOB NOT NULL"
|
|
", object_store_id INTEGER NOT NULL"
|
|
", value_locale BLOB"
|
|
", PRIMARY KEY (index_id, value, object_data_key)"
|
|
", FOREIGN KEY (index_id) "
|
|
"REFERENCES object_store_index(id) "
|
|
", FOREIGN KEY (object_store_id, object_data_key) "
|
|
"REFERENCES object_data(object_store_id, key) "
|
|
") WITHOUT ROWID;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX index_data_value_locale_index "
|
|
"ON index_data (index_id, value_locale, object_data_key, value) "
|
|
"WHERE value_locale IS NOT NULL;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Table `unique_index_data`
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE unique_index_data"
|
|
"( index_id INTEGER NOT NULL"
|
|
", value BLOB NOT NULL"
|
|
", object_store_id INTEGER NOT NULL"
|
|
", object_data_key BLOB NOT NULL"
|
|
", value_locale BLOB"
|
|
", PRIMARY KEY (index_id, value)"
|
|
", FOREIGN KEY (index_id) "
|
|
"REFERENCES object_store_index(id) "
|
|
", FOREIGN KEY (object_store_id, object_data_key) "
|
|
"REFERENCES object_data(object_store_id, key) "
|
|
") WITHOUT ROWID;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX unique_index_data_value_locale_index "
|
|
"ON unique_index_data (index_id, value_locale, object_data_key, value) "
|
|
"WHERE value_locale IS NOT NULL;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = CreateFileTables(aConnection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(kSQLiteSchemaVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom4To5(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom4To5",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv;
|
|
|
|
// All we changed is the type of the version column, so lets try to
|
|
// convert that to an integer, and if we fail, set it to 0.
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT name, version, dataVersion "
|
|
"FROM database"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsString name;
|
|
int32_t intVersion;
|
|
int64_t dataVersion;
|
|
|
|
{
|
|
mozStorageStatementScoper scoper(stmt);
|
|
|
|
bool hasResults;
|
|
rv = stmt->ExecuteStep(&hasResults);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
if (NS_WARN_IF(!hasResults)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsString version;
|
|
rv = stmt->GetString(1, version);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
intVersion = version.ToInteger(&rv);
|
|
if (NS_FAILED(rv)) {
|
|
intVersion = 0;
|
|
}
|
|
|
|
rv = stmt->GetString(0, name);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->GetInt64(2, &dataVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE database"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE database ("
|
|
"name TEXT NOT NULL, "
|
|
"version INTEGER NOT NULL DEFAULT 0, "
|
|
"dataVersion INTEGER NOT NULL"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
|
|
"INSERT INTO database (name, version, dataVersion) "
|
|
"VALUES (:name, :version, :dataVersion)"
|
|
), getter_AddRefs(stmt));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
{
|
|
mozStorageStatementScoper scoper(stmt);
|
|
|
|
rv = stmt->BindStringParameter(0, name);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt32Parameter(1, intVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->BindInt64Parameter(2, dataVersion);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(5);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom5To6(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom5To6",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// First, drop all the indexes we're no longer going to use.
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP INDEX key_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP INDEX ai_key_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP INDEX value_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP INDEX ai_value_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Now, reorder the columns of object_data to put the blob data last. We do
|
|
// this by copying into a temporary table, dropping the original, then copying
|
|
// back into a newly created table.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"object_store_id, "
|
|
"key_value, "
|
|
"data "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT id, object_store_id, key_value, data "
|
|
"FROM object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_data ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"object_store_id INTEGER NOT NULL, "
|
|
"key_value DEFAULT NULL, "
|
|
"data BLOB NOT NULL, "
|
|
"UNIQUE (object_store_id, key_value), "
|
|
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_data "
|
|
"SELECT id, object_store_id, key_value, data "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// We need to add a unique constraint to our ai_object_data table. Copy all
|
|
// the data out of it using a temporary table as before.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"object_store_id, "
|
|
"data "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT id, object_store_id, data "
|
|
"FROM ai_object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE ai_object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE ai_object_data ("
|
|
"id INTEGER PRIMARY KEY AUTOINCREMENT, "
|
|
"object_store_id INTEGER NOT NULL, "
|
|
"data BLOB NOT NULL, "
|
|
"UNIQUE (object_store_id, id), "
|
|
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO ai_object_data "
|
|
"SELECT id, object_store_id, data "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Fix up the index_data table. We're reordering the columns as well as
|
|
// changing the primary key from being a simple id to being a composite.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"index_id, "
|
|
"value, "
|
|
"object_data_key, "
|
|
"object_data_id "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT index_id, value, object_data_key, object_data_id "
|
|
"FROM index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE index_data ("
|
|
"index_id INTEGER NOT NULL, "
|
|
"value NOT NULL, "
|
|
"object_data_key NOT NULL, "
|
|
"object_data_id INTEGER NOT NULL, "
|
|
"PRIMARY KEY (index_id, value, object_data_key), "
|
|
"FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
|
|
"CASCADE, "
|
|
"FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT OR IGNORE INTO index_data "
|
|
"SELECT index_id, value, object_data_key, object_data_id "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX index_data_object_data_id_index "
|
|
"ON index_data (object_data_id);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Fix up the unique_index_data table. We're reordering the columns as well as
|
|
// changing the primary key from being a simple id to being a composite.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"index_id, "
|
|
"value, "
|
|
"object_data_key, "
|
|
"object_data_id "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT index_id, value, object_data_key, object_data_id "
|
|
"FROM unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE unique_index_data ("
|
|
"index_id INTEGER NOT NULL, "
|
|
"value NOT NULL, "
|
|
"object_data_key NOT NULL, "
|
|
"object_data_id INTEGER NOT NULL, "
|
|
"PRIMARY KEY (index_id, value, object_data_key), "
|
|
"UNIQUE (index_id, value), "
|
|
"FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
|
|
"CASCADE "
|
|
"FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO unique_index_data "
|
|
"SELECT index_id, value, object_data_key, object_data_id "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX unique_index_data_object_data_id_index "
|
|
"ON unique_index_data (object_data_id);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Fix up the ai_index_data table. We're reordering the columns as well as
|
|
// changing the primary key from being a simple id to being a composite.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"index_id, "
|
|
"value, "
|
|
"ai_object_data_id "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT index_id, value, ai_object_data_id "
|
|
"FROM ai_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE ai_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE ai_index_data ("
|
|
"index_id INTEGER NOT NULL, "
|
|
"value NOT NULL, "
|
|
"ai_object_data_id INTEGER NOT NULL, "
|
|
"PRIMARY KEY (index_id, value, ai_object_data_id), "
|
|
"FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
|
|
"CASCADE, "
|
|
"FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT OR IGNORE INTO ai_index_data "
|
|
"SELECT index_id, value, ai_object_data_id "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX ai_index_data_ai_object_data_id_index "
|
|
"ON ai_index_data (ai_object_data_id);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Fix up the ai_unique_index_data table. We're reordering the columns as well
|
|
// as changing the primary key from being a simple id to being a composite.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"index_id, "
|
|
"value, "
|
|
"ai_object_data_id "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT index_id, value, ai_object_data_id "
|
|
"FROM ai_unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE ai_unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE ai_unique_index_data ("
|
|
"index_id INTEGER NOT NULL, "
|
|
"value NOT NULL, "
|
|
"ai_object_data_id INTEGER NOT NULL, "
|
|
"UNIQUE (index_id, value), "
|
|
"PRIMARY KEY (index_id, value, ai_object_data_id), "
|
|
"FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
|
|
"CASCADE, "
|
|
"FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO ai_unique_index_data "
|
|
"SELECT index_id, value, ai_object_data_id "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX ai_unique_index_data_ai_object_data_id_index "
|
|
"ON ai_unique_index_data (ai_object_data_id);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(6);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom6To7(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom6To7",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"id, "
|
|
"name, "
|
|
"key_path, "
|
|
"auto_increment"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT id, name, key_path, auto_increment "
|
|
"FROM object_store;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE object_store;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_store ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"auto_increment INTEGER NOT NULL DEFAULT 0, "
|
|
"name TEXT NOT NULL, "
|
|
"key_path TEXT, "
|
|
"UNIQUE (name)"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_store "
|
|
"SELECT id, auto_increment, name, nullif(key_path, '') "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(7);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom7To8(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom7To8",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"id, "
|
|
"object_store_id, "
|
|
"name, "
|
|
"key_path, "
|
|
"unique_index, "
|
|
"object_store_autoincrement"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT id, object_store_id, name, key_path, "
|
|
"unique_index, object_store_autoincrement "
|
|
"FROM object_store_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE object_store_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_store_index ("
|
|
"id INTEGER, "
|
|
"object_store_id INTEGER NOT NULL, "
|
|
"name TEXT NOT NULL, "
|
|
"key_path TEXT NOT NULL, "
|
|
"unique_index INTEGER NOT NULL, "
|
|
"multientry INTEGER NOT NULL, "
|
|
"object_store_autoincrement INTERGER NOT NULL, "
|
|
"PRIMARY KEY (id), "
|
|
"UNIQUE (object_store_id, name), "
|
|
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_store_index "
|
|
"SELECT id, object_store_id, name, key_path, "
|
|
"unique_index, 0, object_store_autoincrement "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(8);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
class CompressDataBlobsFunction final
|
|
: public mozIStorageFunction
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
private:
|
|
~CompressDataBlobsFunction()
|
|
{ }
|
|
|
|
NS_IMETHOD
|
|
OnFunctionCall(mozIStorageValueArray* aArguments,
|
|
nsIVariant** aResult) override
|
|
{
|
|
MOZ_ASSERT(aArguments);
|
|
MOZ_ASSERT(aResult);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"CompressDataBlobsFunction::OnFunctionCall",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
uint32_t argc;
|
|
nsresult rv = aArguments->GetNumEntries(&argc);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (argc != 1) {
|
|
NS_WARNING("Don't call me with the wrong number of arguments!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
int32_t type;
|
|
rv = aArguments->GetTypeOfIndex(0, &type);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (type != mozIStorageStatement::VALUE_TYPE_BLOB) {
|
|
NS_WARNING("Don't call me with the wrong type of arguments!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
const uint8_t* uncompressed;
|
|
uint32_t uncompressedLength;
|
|
rv = aArguments->GetSharedBlob(0, &uncompressedLength, &uncompressed);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
|
|
UniqueFreePtr<uint8_t> compressed(
|
|
static_cast<uint8_t*>(malloc(compressedLength)));
|
|
if (NS_WARN_IF(!compressed)) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
snappy::RawCompress(reinterpret_cast<const char*>(uncompressed),
|
|
uncompressedLength,
|
|
reinterpret_cast<char*>(compressed.get()),
|
|
&compressedLength);
|
|
|
|
std::pair<uint8_t *, int> data(compressed.release(),
|
|
int(compressedLength));
|
|
|
|
nsCOMPtr<nsIVariant> result = new mozilla::storage::AdoptedBlobVariant(data);
|
|
|
|
result.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom8To9_0(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom8To9_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
// We no longer use the dataVersion column.
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"UPDATE database SET dataVersion = 0;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageFunction> compressor = new CompressDataBlobsFunction();
|
|
|
|
NS_NAMED_LITERAL_CSTRING(compressorName, "compress");
|
|
|
|
rv = aConnection->CreateFunction(compressorName, 1, compressor);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Turn off foreign key constraints before we do anything here.
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"UPDATE object_data SET data = compress(data);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"UPDATE ai_object_data SET data = compress(data);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->RemoveFunction(compressorName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(9, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom9_0To10_0(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom9_0To10_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"ALTER TABLE object_data ADD COLUMN file_ids TEXT;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"ALTER TABLE ai_object_data ADD COLUMN file_ids TEXT;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = CreateFileTables(aConnection);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(10, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom10_0To11_0(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom10_0To11_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"id, "
|
|
"object_store_id, "
|
|
"name, "
|
|
"key_path, "
|
|
"unique_index, "
|
|
"multientry"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT id, object_store_id, name, key_path, "
|
|
"unique_index, multientry "
|
|
"FROM object_store_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE object_store_index;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_store_index ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"object_store_id INTEGER NOT NULL, "
|
|
"name TEXT NOT NULL, "
|
|
"key_path TEXT NOT NULL, "
|
|
"unique_index INTEGER NOT NULL, "
|
|
"multientry INTEGER NOT NULL, "
|
|
"UNIQUE (object_store_id, name), "
|
|
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_store_index "
|
|
"SELECT id, object_store_id, name, key_path, "
|
|
"unique_index, multientry "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TRIGGER object_data_insert_trigger;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_data (object_store_id, key_value, data, file_ids) "
|
|
"SELECT object_store_id, id, data, file_ids "
|
|
"FROM ai_object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_insert_trigger "
|
|
"AFTER INSERT ON object_data "
|
|
"FOR EACH ROW "
|
|
"WHEN NEW.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(NULL, NEW.file_ids); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO index_data (index_id, value, object_data_key, object_data_id) "
|
|
"SELECT ai_index_data.index_id, ai_index_data.value, ai_index_data.ai_object_data_id, object_data.id "
|
|
"FROM ai_index_data "
|
|
"INNER JOIN object_store_index ON "
|
|
"object_store_index.id = ai_index_data.index_id "
|
|
"INNER JOIN object_data ON "
|
|
"object_data.object_store_id = object_store_index.object_store_id AND "
|
|
"object_data.key_value = ai_index_data.ai_object_data_id;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO unique_index_data (index_id, value, object_data_key, object_data_id) "
|
|
"SELECT ai_unique_index_data.index_id, ai_unique_index_data.value, ai_unique_index_data.ai_object_data_id, object_data.id "
|
|
"FROM ai_unique_index_data "
|
|
"INNER JOIN object_store_index ON "
|
|
"object_store_index.id = ai_unique_index_data.index_id "
|
|
"INNER JOIN object_data ON "
|
|
"object_data.object_store_id = object_store_index.object_store_id AND "
|
|
"object_data.key_value = ai_unique_index_data.ai_object_data_id;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"UPDATE object_store "
|
|
"SET auto_increment = (SELECT max(id) FROM ai_object_data) + 1 "
|
|
"WHERE auto_increment;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE ai_unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE ai_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE ai_object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(11, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
class EncodeKeysFunction final
|
|
: public mozIStorageFunction
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
private:
|
|
~EncodeKeysFunction()
|
|
{ }
|
|
|
|
NS_IMETHOD
|
|
OnFunctionCall(mozIStorageValueArray* aArguments,
|
|
nsIVariant** aResult) override
|
|
{
|
|
MOZ_ASSERT(aArguments);
|
|
MOZ_ASSERT(aResult);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"EncodeKeysFunction::OnFunctionCall",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
uint32_t argc;
|
|
nsresult rv = aArguments->GetNumEntries(&argc);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (argc != 1) {
|
|
NS_WARNING("Don't call me with the wrong number of arguments!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
int32_t type;
|
|
rv = aArguments->GetTypeOfIndex(0, &type);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
Key key;
|
|
if (type == mozIStorageStatement::VALUE_TYPE_INTEGER) {
|
|
int64_t intKey;
|
|
aArguments->GetInt64(0, &intKey);
|
|
key.SetFromInteger(intKey);
|
|
} else if (type == mozIStorageStatement::VALUE_TYPE_TEXT) {
|
|
nsString stringKey;
|
|
aArguments->GetString(0, stringKey);
|
|
key.SetFromString(stringKey);
|
|
} else {
|
|
NS_WARNING("Don't call me with the wrong type of arguments!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
const nsCString& buffer = key.GetBuffer();
|
|
|
|
std::pair<const void *, int> data(static_cast<const void*>(buffer.get()),
|
|
int(buffer.Length()));
|
|
|
|
nsCOMPtr<nsIVariant> result = new mozilla::storage::BlobVariant(data);
|
|
|
|
result.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom11_0To12_0(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom11_0To12_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
NS_NAMED_LITERAL_CSTRING(encoderName, "encode");
|
|
|
|
nsCOMPtr<mozIStorageFunction> encoder = new EncodeKeysFunction();
|
|
|
|
nsresult rv = aConnection->CreateFunction(encoderName, 1, encoder);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"object_store_id, "
|
|
"key_value, "
|
|
"data, "
|
|
"file_ids "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT id, object_store_id, encode(key_value), data, file_ids "
|
|
"FROM object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE object_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE object_data ("
|
|
"id INTEGER PRIMARY KEY, "
|
|
"object_store_id INTEGER NOT NULL, "
|
|
"key_value BLOB DEFAULT NULL, "
|
|
"file_ids TEXT, "
|
|
"data BLOB NOT NULL, "
|
|
"UNIQUE (object_store_id, key_value), "
|
|
"FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO object_data "
|
|
"SELECT id, object_store_id, key_value, file_ids, data "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_insert_trigger "
|
|
"AFTER INSERT ON object_data "
|
|
"FOR EACH ROW "
|
|
"WHEN NEW.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(NULL, NEW.file_ids); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_update_trigger "
|
|
"AFTER UPDATE OF file_ids ON object_data "
|
|
"FOR EACH ROW "
|
|
"WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TRIGGER object_data_delete_trigger "
|
|
"AFTER DELETE ON object_data "
|
|
"FOR EACH ROW WHEN OLD.file_ids IS NOT NULL "
|
|
"BEGIN "
|
|
"SELECT update_refcount(OLD.file_ids, NULL); "
|
|
"END;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"index_id, "
|
|
"value, "
|
|
"object_data_key, "
|
|
"object_data_id "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT index_id, encode(value), encode(object_data_key), object_data_id "
|
|
"FROM index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE index_data ("
|
|
"index_id INTEGER NOT NULL, "
|
|
"value BLOB NOT NULL, "
|
|
"object_data_key BLOB NOT NULL, "
|
|
"object_data_id INTEGER NOT NULL, "
|
|
"PRIMARY KEY (index_id, value, object_data_key), "
|
|
"FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
|
|
"CASCADE, "
|
|
"FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO index_data "
|
|
"SELECT index_id, value, object_data_key, object_data_id "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX index_data_object_data_id_index "
|
|
"ON index_data (object_data_id);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TEMPORARY TABLE temp_upgrade ("
|
|
"index_id, "
|
|
"value, "
|
|
"object_data_key, "
|
|
"object_data_id "
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO temp_upgrade "
|
|
"SELECT index_id, encode(value), encode(object_data_key), object_data_id "
|
|
"FROM unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE unique_index_data;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE unique_index_data ("
|
|
"index_id INTEGER NOT NULL, "
|
|
"value BLOB NOT NULL, "
|
|
"object_data_key BLOB NOT NULL, "
|
|
"object_data_id INTEGER NOT NULL, "
|
|
"PRIMARY KEY (index_id, value, object_data_key), "
|
|
"UNIQUE (index_id, value), "
|
|
"FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE "
|
|
"CASCADE "
|
|
"FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE "
|
|
"CASCADE"
|
|
");"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"INSERT INTO unique_index_data "
|
|
"SELECT index_id, value, object_data_key, object_data_id "
|
|
"FROM temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"DROP TABLE temp_upgrade;"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE INDEX unique_index_data_object_data_id_index "
|
|
"ON unique_index_data (object_data_id);"
|
|
));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->RemoveFunction(encoderName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(12, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom12_0To13_0(mozIStorageConnection* aConnection,
|
|
bool* aVacuumNeeded)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom12_0To13_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
nsresult rv;
|
|
|
|
#ifdef IDB_MOBILE
|
|
int32_t defaultPageSize;
|
|
rv = aConnection->GetDefaultPageSize(&defaultPageSize);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Enable auto_vacuum mode and update the page size to the platform default.
|
|
nsAutoCString upgradeQuery("PRAGMA auto_vacuum = FULL; PRAGMA page_size = ");
|
|
upgradeQuery.AppendInt(defaultPageSize);
|
|
|
|
rv = aConnection->ExecuteSimpleSQL(upgradeQuery);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
*aVacuumNeeded = true;
|
|
#endif
|
|
|
|
rv = aConnection->SetSchemaVersion(MakeSchemaVersion(13, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom13_0To14_0(mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
// The only change between 13 and 14 was a different structured
|
|
// clone format, but it's backwards-compatible.
|
|
nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(14, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom14_0To15_0(mozIStorageConnection* aConnection)
|
|
{
|
|
// The only change between 14 and 15 was a different structured
|
|
// clone format, but it's backwards-compatible.
|
|
nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(15, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom15_0To16_0(mozIStorageConnection* aConnection)
|
|
{
|
|
// The only change between 15 and 16 was a different structured
|
|
// clone format, but it's backwards-compatible.
|
|
nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(16, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom16_0To17_0(mozIStorageConnection* aConnection)
|
|
{
|
|
// The only change between 16 and 17 was a different structured
|
|
// clone format, but it's backwards-compatible.
|
|
nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(17, 0));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
class UpgradeSchemaFrom17_0To18_0Helper final
|
|
{
|
|
class InsertIndexDataValuesFunction;
|
|
class UpgradeKeyFunction;
|
|
|
|
public:
|
|
static nsresult
|
|
DoUpgrade(mozIStorageConnection* aConnection, const nsACString& aOrigin);
|
|
|
|
private:
|
|
static nsresult
|
|
DoUpgradeInternal(mozIStorageConnection* aConnection,
|
|
const nsACString& aOrigin);
|
|
|
|
UpgradeSchemaFrom17_0To18_0Helper()
|
|
{
|
|
MOZ_ASSERT_UNREACHABLE("Don't create instances of this class!");
|
|
}
|
|
|
|
~UpgradeSchemaFrom17_0To18_0Helper()
|
|
{
|
|
MOZ_ASSERT_UNREACHABLE("Don't create instances of this class!");
|
|
}
|
|
};
|
|
|
|
class UpgradeSchemaFrom17_0To18_0Helper::InsertIndexDataValuesFunction final
|
|
: public mozIStorageFunction
|
|
{
|
|
public:
|
|
InsertIndexDataValuesFunction()
|
|
{ }
|
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
private:
|
|
~InsertIndexDataValuesFunction()
|
|
{ }
|
|
|
|
NS_DECL_MOZISTORAGEFUNCTION
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper::
|
|
InsertIndexDataValuesFunction,
|
|
mozIStorageFunction);
|
|
|
|
NS_IMETHODIMP
|
|
UpgradeSchemaFrom17_0To18_0Helper::
|
|
InsertIndexDataValuesFunction::OnFunctionCall(mozIStorageValueArray* aValues,
|
|
nsIVariant** _retval)
|
|
{
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
MOZ_ASSERT(!IsOnBackgroundThread());
|
|
MOZ_ASSERT(aValues);
|
|
MOZ_ASSERT(_retval);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
uint32_t argCount;
|
|
MOZ_ALWAYS_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()
|
|
{ }
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
};
|
|
|
|
nsresult
|
|
UpgradeSchemaFrom19_0To20_0(nsIFile* aFMDirectory,
|
|
mozIStorageConnection* aConnection)
|
|
{
|
|
AssertIsOnIOThread();
|
|
MOZ_ASSERT(aConnection);
|
|
|
|
PROFILER_LABEL("IndexedDB",
|
|
"UpgradeSchemaFrom19_0To20_0",
|
|
js::ProfileEntry::Category::STORAGE);
|
|
|
|
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;
|
|
}
|
|
|
|
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,
|
|
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);
|
|
|
|
rv = fileUrl->SetQuery(NS_LITERAL_CSTRING("persistenceType=") + type +
|
|
NS_LITERAL_CSTRING("&group=") + aGroup +
|
|
NS_LITERAL_CSTRING("&origin=") + aOrigin +
|
|
NS_LITERAL_CSTRING("&cache=private"));
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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* aConnection);
|
|
|
|
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()
|
|
{ }
|
|
|
|
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);
|
|
|
|
virtual
|
|
~ConnectionRunnable()
|
|
{ }
|
|
};
|
|
|
|
class ConnectionPool::IdleConnectionRunnable final
|
|
: public ConnectionRunnable
|
|
{
|
|
bool mNeedsCheckpoint;
|
|
|
|
public:
|
|
IdleConnectionRunnable(DatabaseInfo* aDatabaseInfo, bool aNeedsCheckpoint)
|
|
: ConnectionRunnable(aDatabaseInfo)
|
|
, mNeedsCheckpoint(aNeedsCheckpoint)
|
|
{ }
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
private:
|
|
~IdleConnectionRunnable()
|
|
{ }
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
};
|
|
|
|
class ConnectionPool::CloseConnectionRunnable final
|
|
: public ConnectionRunnable
|
|
{
|
|
public:
|
|
explicit
|
|
CloseConnectionRunnable(DatabaseInfo* aDatabaseInfo)
|
|
: ConnectionRunnable(aDatabaseInfo)
|
|
{ }
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
private:
|
|
~CloseConnectionRunnable()
|
|
{ }
|
|
|
|
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()
|
|
{ }
|
|
};
|
|
|
|
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();
|
|
|
|
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;
|
|
}
|
|
|
|
private:
|
|
~ThreadRunnable();
|
|
|
|
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();
|
|
}
|
|
|
|
virtual
|
|
~DatabaseOperationBase()
|
|
{
|
|
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);
|
|
|
|
virtual
|
|
~TransactionDatabaseOperationBase();
|
|
|
|
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();
|
|
|
|
// IPDL methods are only called by IPDL.
|
|
virtual void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
virtual bool
|
|
RecvDeleteMe() override;
|
|
|
|
virtual bool
|
|
RecvIncrementLoggingRequestSerialNumber() override;
|
|
|
|
virtual PBackgroundIDBFactoryRequestParent*
|
|
AllocPBackgroundIDBFactoryRequestParent(const FactoryRequestParams& aParams)
|
|
override;
|
|
|
|
virtual bool
|
|
RecvPBackgroundIDBFactoryRequestConstructor(
|
|
PBackgroundIDBFactoryRequestParent* aActor,
|
|
const FactoryRequestParams& aParams)
|
|
override;
|
|
|
|
virtual bool
|
|
DeallocPBackgroundIDBFactoryRequestParent(
|
|
PBackgroundIDBFactoryRequestParent* aActor)
|
|
override;
|
|
|
|
virtual PBackgroundIDBDatabaseParent*
|
|
AllocPBackgroundIDBDatabaseParent(
|
|
const DatabaseSpec& aSpec,
|
|
PBackgroundIDBFactoryRequestParent* aRequest)
|
|
override;
|
|
|
|
virtual 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()
|
|
{
|
|
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 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,
|
|
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;
|
|
}
|
|
|
|
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()
|
|
{
|
|
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.
|
|
virtual void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
virtual PBackgroundIDBDatabaseFileParent*
|
|
AllocPBackgroundIDBDatabaseFileParent(PBlobParent* aBlobParent)
|
|
override;
|
|
|
|
virtual bool
|
|
DeallocPBackgroundIDBDatabaseFileParent(
|
|
PBackgroundIDBDatabaseFileParent* aActor)
|
|
override;
|
|
|
|
virtual PBackgroundIDBDatabaseRequestParent*
|
|
AllocPBackgroundIDBDatabaseRequestParent(const DatabaseRequestParams& aParams)
|
|
override;
|
|
|
|
virtual bool
|
|
RecvPBackgroundIDBDatabaseRequestConstructor(
|
|
PBackgroundIDBDatabaseRequestParent* aActor,
|
|
const DatabaseRequestParams& aParams)
|
|
override;
|
|
|
|
virtual bool
|
|
DeallocPBackgroundIDBDatabaseRequestParent(
|
|
PBackgroundIDBDatabaseRequestParent* aActor)
|
|
override;
|
|
|
|
virtual PBackgroundIDBTransactionParent*
|
|
AllocPBackgroundIDBTransactionParent(
|
|
const nsTArray<nsString>& aObjectStoreNames,
|
|
const Mode& aMode)
|
|
override;
|
|
|
|
virtual bool
|
|
RecvPBackgroundIDBTransactionConstructor(
|
|
PBackgroundIDBTransactionParent* aActor,
|
|
InfallibleTArray<nsString>&& aObjectStoreNames,
|
|
const Mode& aMode)
|
|
override;
|
|
|
|
virtual bool
|
|
DeallocPBackgroundIDBTransactionParent(
|
|
PBackgroundIDBTransactionParent* aActor)
|
|
override;
|
|
|
|
virtual PBackgroundIDBVersionChangeTransactionParent*
|
|
AllocPBackgroundIDBVersionChangeTransactionParent(
|
|
const uint64_t& aCurrentVersion,
|
|
const uint64_t& aRequestedVersion,
|
|
const int64_t& aNextObjectStoreId,
|
|
const int64_t& aNextIndexId)
|
|
override;
|
|
|
|
virtual bool
|
|
DeallocPBackgroundIDBVersionChangeTransactionParent(
|
|
PBackgroundIDBVersionChangeTransactionParent* aActor)
|
|
override;
|
|
|
|
virtual PBackgroundMutableFileParent*
|
|
AllocPBackgroundMutableFileParent(const nsString& aName,
|
|
const nsString& aType) override;
|
|
|
|
virtual bool
|
|
DeallocPBackgroundMutableFileParent(PBackgroundMutableFileParent* aActor)
|
|
override;
|
|
|
|
virtual bool
|
|
RecvDeleteMe() override;
|
|
|
|
virtual bool
|
|
RecvBlocked() override;
|
|
|
|
virtual bool
|
|
RecvClose() override;
|
|
};
|
|
|
|
class Database::StartTransactionOp final
|
|
: public TransactionDatabaseOperationBase
|
|
{
|
|
friend class Database;
|
|
|
|
private:
|
|
explicit
|
|
StartTransactionOp(TransactionBase* aTransaction)
|
|
: TransactionDatabaseOperationBase(aTransaction,
|
|
/* aLoggingSerialNumber */ 0)
|
|
{ }
|
|
|
|
~StartTransactionOp()
|
|
{ }
|
|
|
|
virtual void
|
|
RunOnConnectionThread() override;
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual nsresult
|
|
SendSuccessResult() override;
|
|
|
|
virtual bool
|
|
SendFailureResult(nsresult aResultCode) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
// 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
|
|
|
|
virtual void
|
|
TransactionFinishedBeforeUnblock() override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
bool
|
|
IsSameProcessActor();
|
|
|
|
// Only called by TransactionBase.
|
|
virtual void
|
|
SendCompleteNotification(nsresult aResult) override;
|
|
|
|
// IPDL methods are only called by IPDL.
|
|
virtual void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
virtual bool
|
|
RecvDeleteMe() override;
|
|
|
|
virtual bool
|
|
RecvCommit() override;
|
|
|
|
virtual bool
|
|
RecvAbort(const nsresult& aResultCode) override;
|
|
|
|
virtual PBackgroundIDBRequestParent*
|
|
AllocPBackgroundIDBRequestParent(const RequestParams& aParams) override;
|
|
|
|
virtual bool
|
|
RecvPBackgroundIDBRequestConstructor(PBackgroundIDBRequestParent* aActor,
|
|
const RequestParams& aParams)
|
|
override;
|
|
|
|
virtual bool
|
|
DeallocPBackgroundIDBRequestParent(PBackgroundIDBRequestParent* aActor)
|
|
override;
|
|
|
|
virtual PBackgroundIDBCursorParent*
|
|
AllocPBackgroundIDBCursorParent(const OpenCursorParams& aParams) override;
|
|
|
|
virtual bool
|
|
RecvPBackgroundIDBCursorConstructor(PBackgroundIDBCursorParent* aActor,
|
|
const OpenCursorParams& aParams)
|
|
override;
|
|
|
|
virtual 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();
|
|
|
|
bool
|
|
IsSameProcessActor();
|
|
|
|
// Only called by OpenDatabaseOp.
|
|
bool
|
|
CopyDatabaseMetadata();
|
|
|
|
void
|
|
SetActorAlive();
|
|
|
|
// Only called by TransactionBase.
|
|
virtual void
|
|
UpdateMetadata(nsresult aResult) override;
|
|
|
|
// Only called by TransactionBase.
|
|
virtual void
|
|
SendCompleteNotification(nsresult aResult) override;
|
|
|
|
// IPDL methods are only called by IPDL.
|
|
virtual void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
virtual bool
|
|
RecvDeleteMe() override;
|
|
|
|
virtual bool
|
|
RecvCommit() override;
|
|
|
|
virtual bool
|
|
RecvAbort(const nsresult& aResultCode) override;
|
|
|
|
virtual bool
|
|
RecvCreateObjectStore(const ObjectStoreMetadata& aMetadata) override;
|
|
|
|
virtual bool
|
|
RecvDeleteObjectStore(const int64_t& aObjectStoreId) override;
|
|
|
|
virtual bool
|
|
RecvRenameObjectStore(const int64_t& aObjectStoreId,
|
|
const nsString& aName) override;
|
|
|
|
virtual bool
|
|
RecvCreateIndex(const int64_t& aObjectStoreId,
|
|
const IndexMetadata& aMetadata) override;
|
|
|
|
virtual bool
|
|
RecvDeleteIndex(const int64_t& aObjectStoreId,
|
|
const int64_t& aIndexId) override;
|
|
|
|
virtual bool
|
|
RecvRenameIndex(const int64_t& aObjectStoreId,
|
|
const int64_t& aIndexId,
|
|
const nsString& aName) override;
|
|
|
|
virtual PBackgroundIDBRequestParent*
|
|
AllocPBackgroundIDBRequestParent(const RequestParams& aParams) override;
|
|
|
|
virtual bool
|
|
RecvPBackgroundIDBRequestConstructor(PBackgroundIDBRequestParent* aActor,
|
|
const RequestParams& aParams)
|
|
override;
|
|
|
|
virtual bool
|
|
DeallocPBackgroundIDBRequestParent(PBackgroundIDBRequestParent* aActor)
|
|
override;
|
|
|
|
virtual PBackgroundIDBCursorParent*
|
|
AllocPBackgroundIDBCursorParent(const OpenCursorParams& aParams) override;
|
|
|
|
virtual bool
|
|
RecvPBackgroundIDBCursorConstructor(PBackgroundIDBCursorParent* aActor,
|
|
const OpenCursorParams& aParams)
|
|
override;
|
|
|
|
virtual 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;
|
|
}
|
|
|
|
virtual void
|
|
NoteActiveState() override;
|
|
|
|
virtual void
|
|
NoteInactiveState() override;
|
|
|
|
virtual PBackgroundParent*
|
|
GetBackgroundParent() const override;
|
|
|
|
virtual already_AddRefed<nsISupports>
|
|
CreateStream(bool aReadOnly) override;
|
|
|
|
virtual already_AddRefed<BlobImpl>
|
|
CreateBlobImpl() override;
|
|
|
|
private:
|
|
MutableFile(nsIFile* aFile,
|
|
Database* aDatabase,
|
|
FileInfo* aFileInfo);
|
|
|
|
~MutableFile();
|
|
|
|
virtual PBackgroundFileHandleParent*
|
|
AllocPBackgroundFileHandleParent(const FileMode& aMode) override;
|
|
|
|
virtual bool
|
|
RecvPBackgroundFileHandleConstructor(PBackgroundFileHandleParent* aActor,
|
|
const FileMode& aMode) override;
|
|
|
|
virtual bool
|
|
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);
|
|
|
|
virtual
|
|
~FactoryOp()
|
|
{
|
|
// 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;
|
|
|
|
// Should only be called by Run().
|
|
virtual void
|
|
SendResults() = 0;
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
// Common nsIRunnable implementation that subclasses may not override.
|
|
NS_IMETHOD
|
|
Run() final;
|
|
|
|
// OpenDirectoryListener overrides.
|
|
virtual void
|
|
DirectoryLockAcquired(DirectoryLock* aLock) override;
|
|
|
|
virtual void
|
|
DirectoryLockFailed() override;
|
|
|
|
// IPDL methods.
|
|
virtual void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
virtual bool
|
|
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;
|
|
|
|
public:
|
|
OpenDatabaseOp(Factory* aFactory,
|
|
already_AddRefed<ContentParent> aContentParent,
|
|
const CommonFactoryRequestParams& aParams);
|
|
|
|
bool
|
|
IsOtherProcessActor() const
|
|
{
|
|
return mOptionalContentParentId.isSome();
|
|
}
|
|
|
|
private:
|
|
~OpenDatabaseOp()
|
|
{
|
|
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();
|
|
|
|
virtual void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
virtual nsresult
|
|
DatabaseOpen() override;
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork() override;
|
|
|
|
virtual nsresult
|
|
BeginVersionChange() override;
|
|
|
|
virtual void
|
|
NoteDatabaseClosed(Database* aDatabase) override;
|
|
|
|
virtual void
|
|
SendBlockedNotification() override;
|
|
|
|
virtual nsresult
|
|
DispatchToWorkThread() override;
|
|
|
|
virtual void
|
|
SendResults() override;
|
|
|
|
static nsresult
|
|
UpdateLocaleAwareIndex(mozIStorageConnection* aConnection,
|
|
const IndexMetadata& aIndexMetadata,
|
|
const nsCString& aLocale);
|
|
};
|
|
|
|
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()
|
|
{
|
|
MOZ_ASSERT(!mOpenDatabaseOp);
|
|
}
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual nsresult
|
|
SendSuccessResult() override;
|
|
|
|
virtual bool
|
|
SendFailureResult(nsresult aResultCode) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
void
|
|
LoadPreviousVersion(nsIFile* aDatabaseFile);
|
|
|
|
virtual nsresult
|
|
DatabaseOpen() override;
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork() override;
|
|
|
|
virtual nsresult
|
|
BeginVersionChange() override;
|
|
|
|
virtual void
|
|
NoteDatabaseClosed(Database* aDatabase) override;
|
|
|
|
virtual void
|
|
SendBlockedNotification() override;
|
|
|
|
virtual nsresult
|
|
DispatchToWorkThread() override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
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);
|
|
|
|
virtual
|
|
~DatabaseOp()
|
|
{
|
|
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.
|
|
virtual 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()
|
|
{ }
|
|
|
|
nsresult
|
|
CreateMutableFile(MutableFile** aMutableFile);
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork() override;
|
|
|
|
virtual void
|
|
SendResults() override;
|
|
};
|
|
|
|
class VersionChangeTransactionOp
|
|
: public TransactionDatabaseOperationBase
|
|
{
|
|
public:
|
|
virtual void
|
|
Cleanup() override;
|
|
|
|
protected:
|
|
explicit VersionChangeTransactionOp(VersionChangeTransaction* aTransaction)
|
|
: TransactionDatabaseOperationBase(aTransaction)
|
|
{ }
|
|
|
|
virtual
|
|
~VersionChangeTransactionOp()
|
|
{ }
|
|
|
|
private:
|
|
virtual nsresult
|
|
SendSuccessResult() override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
nsresult
|
|
InsertDataFromObjectStore(DatabaseConnection* aConnection);
|
|
|
|
nsresult
|
|
InsertDataFromObjectStoreInternal(DatabaseConnection* aConnection);
|
|
|
|
virtual bool
|
|
Init(TransactionBase* aTransaction) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
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()
|
|
{ }
|
|
|
|
nsresult
|
|
RemoveReferencesToIndex(DatabaseConnection* aConnection,
|
|
const Key& aObjectDataKey,
|
|
nsTArray<IndexDataValue>& aIndexValues);
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
};
|
|
|
|
class NormalTransactionOp
|
|
: public TransactionDatabaseOperationBase
|
|
, public PBackgroundIDBRequestParent
|
|
{
|
|
#ifdef DEBUG
|
|
bool mResponseSent;
|
|
#endif
|
|
|
|
public:
|
|
virtual void
|
|
Cleanup() override;
|
|
|
|
protected:
|
|
explicit NormalTransactionOp(TransactionBase* aTransaction)
|
|
: TransactionDatabaseOperationBase(aTransaction)
|
|
#ifdef DEBUG
|
|
, mResponseSent(false)
|
|
#endif
|
|
{ }
|
|
|
|
virtual
|
|
~NormalTransactionOp()
|
|
{ }
|
|
|
|
// 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:
|
|
virtual nsresult
|
|
SendPreprocessInfo() override;
|
|
|
|
virtual nsresult
|
|
SendSuccessResult() override;
|
|
|
|
virtual bool
|
|
SendFailureResult(nsresult aResultCode) override;
|
|
|
|
// IPDL methods.
|
|
virtual void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
virtual bool
|
|
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()
|
|
{ }
|
|
|
|
nsresult
|
|
RemoveOldIndexDataValues(DatabaseConnection* aConnection);
|
|
|
|
virtual bool
|
|
Init(TransactionBase* aTransaction) override;
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual void
|
|
GetResponse(RequestResponse& aResponse) override;
|
|
|
|
virtual 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::Iterator mIter;
|
|
|
|
public:
|
|
explicit SCInputStream(const JSStructuredCloneData& aData)
|
|
: mData(aData)
|
|
, mIter(aData.Start())
|
|
{ }
|
|
|
|
private:
|
|
virtual ~SCInputStream()
|
|
{ }
|
|
|
|
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()
|
|
{ }
|
|
|
|
template <bool aForPreprocess, typename T>
|
|
nsresult
|
|
ConvertResponse(StructuredCloneReadInfo& aInfo, T& aResult);
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual bool
|
|
HasPreprocessInfo() override;
|
|
|
|
virtual nsresult
|
|
GetPreprocessParams(PreprocessParams& aParams) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual 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))
|
|
{ }
|
|
|
|
virtual
|
|
~IndexRequestOpBase()
|
|
{ }
|
|
|
|
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()
|
|
{ }
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual 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()
|
|
{
|
|
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.
|
|
virtual void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
virtual bool
|
|
RecvDeleteMe() override;
|
|
|
|
virtual bool
|
|
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);
|
|
}
|
|
|
|
virtual
|
|
~CursorOpBase()
|
|
{ }
|
|
|
|
virtual bool
|
|
SendFailureResult(nsresult aResultCode) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
void
|
|
GetRangeKeyInfo(bool aLowerBound, Key* aKey, bool* aOpen);
|
|
|
|
nsresult
|
|
DoObjectStoreDatabaseWork(DatabaseConnection* aConnection);
|
|
|
|
nsresult
|
|
DoObjectStoreKeyDatabaseWork(DatabaseConnection* aConnection);
|
|
|
|
nsresult
|
|
DoIndexDatabaseWork(DatabaseConnection* aConnection);
|
|
|
|
nsresult
|
|
DoIndexKeyDatabaseWork(DatabaseConnection* aConnection);
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
virtual nsresult
|
|
DoDatabaseWork(DatabaseConnection* aConnection) override;
|
|
|
|
virtual 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();
|
|
|
|
// IPDL methods are only called by IPDL.
|
|
virtual void
|
|
ActorDestroy(ActorDestroyReason aWhy) override;
|
|
|
|
virtual bool
|
|
RecvDeleteMe() override;
|
|
|
|
virtual bool
|
|
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() {}
|
|
|
|
NS_DECL_NSIRUNNABLE
|
|
};
|
|
|
|
class FlushPendingFileDeletionsRunnable final
|
|
: public Runnable
|
|
{
|
|
private:
|
|
~FlushPendingFileDeletionsRunnable()
|
|
{ }
|
|
|
|
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()
|
|
{ }
|
|
|
|
private:
|
|
virtual void
|
|
OnPromptComplete(PermissionValue aPermissionValue) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
NS_DECLARE_STATIC_IID_ACCESSOR(BLOB_IMPL_STORED_FILE_IID)
|
|
|
|
virtual 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)
|
|
|
|
virtual mozilla::dom::quota::Client::Type
|
|
GetType() override;
|
|
|
|
virtual nsresult
|
|
InitOrigin(PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
const AtomicBool& aCanceled,
|
|
UsageInfo* aUsageInfo) override;
|
|
|
|
virtual nsresult
|
|
GetUsageForOrigin(PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
const AtomicBool& aCanceled,
|
|
UsageInfo* aUsageInfo) override;
|
|
|
|
virtual void
|
|
OnOriginClearCompleted(PersistenceType aPersistenceType,
|
|
const nsACString& aOrigin)
|
|
override;
|
|
|
|
virtual void
|
|
ReleaseIOThreadObjects() override;
|
|
|
|
virtual void
|
|
AbortOperations(const nsACString& aOrigin) override;
|
|
|
|
virtual void
|
|
AbortOperationsForProcess(ContentParentId aContentParentId) override;
|
|
|
|
virtual void
|
|
StartIdleMaintenance() override;
|
|
|
|
virtual void
|
|
StopIdleMaintenance() override;
|
|
|
|
virtual void
|
|
ShutdownWorkThreads() override;
|
|
|
|
virtual void
|
|
DidInitialize(QuotaManager* aQuotaManager) override;
|
|
|
|
virtual void
|
|
WillShutdown() override;
|
|
|
|
private:
|
|
~QuotaClient();
|
|
|
|
nsresult
|
|
GetDirectory(PersistenceType aPersistenceType,
|
|
const nsACString& aOrigin,
|
|
nsIFile** aDirectory);
|
|
|
|
nsresult
|
|
GetUsageForDirectoryInternal(nsIFile* aDirectory,
|
|
const AtomicBool& aCanceled,
|
|
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;
|
|
nsresult mResultCode;
|
|
Atomic<bool> mAborted;
|
|
State mState;
|
|
|
|
public:
|
|
explicit Maintenance(QuotaClient* aQuotaClient)
|
|
: mQuotaClient(aQuotaClient)
|
|
, mStartTime(PR_Now())
|
|
, mResultCode(NS_OK)
|
|
, 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()
|
|
{
|
|
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.
|
|
virtual void
|
|
DirectoryLockAcquired(DirectoryLock* aLock) override;
|
|
|
|
virtual 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()
|
|
{ }
|
|
|
|
// 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;
|
|
|
|
#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
|
|
}
|
|
}
|
|
|
|
} // 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:
|
|
virtual nsresult
|
|
Cleanup(FileManager* aFileManager, int64_t aId)
|
|
{
|
|
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(),
|
|
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);
|
|
}
|
|
|
|
TransactionInfo* 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;
|
|
if (NS_SUCCEEDED(NS_NewThread(getter_AddRefs(newThread), runnable))) {
|
|
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 thread name. Both PR_SetCurrentThreadName() and
|
|
// profiler_register_thread() copy the string so we don't need to keep it.
|
|
const nsPrintfCString threadName("IndexedDB #%lu", mSerialNumber);
|
|
|
|
PR_SetCurrentThreadName(threadName.get());
|
|
}
|
|
|
|
{
|
|
// 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();
|
|
}
|
|
|
|
bool
|
|
Factory::RecvDeleteMe()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
return PBackgroundIDBFactoryParent::Send__delete__(this);
|
|
}
|
|
|
|
bool
|
|
Factory::RecvIncrementLoggingRequestSerialNumber()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mLoggingInfo);
|
|
|
|
mLoggingInfo->NextRequestSN();
|
|
return true;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
bool
|
|
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 true;
|
|
}
|
|
|
|
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,
|
|
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)
|
|
, 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();
|
|
}
|
|
|
|
bool
|
|
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 true;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
bool
|
|
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 true;
|
|
}
|
|
|
|
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 true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool
|
|
Database::RecvDeleteMe()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
return PBackgroundIDBDatabaseParent::Send__delete__(this);
|
|
}
|
|
|
|
bool
|
|
Database::RecvBlocked()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(mClosed)) {
|
|
return false;
|
|
}
|
|
|
|
DatabaseActorInfo* info;
|
|
MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(Id(), &info));
|
|
|
|
MOZ_ASSERT(info->mLiveDatabases.Contains(this));
|
|
MOZ_ASSERT(info->mWaitingFactoryOp);
|
|
|
|
info->mWaitingFactoryOp->NoteDatabaseBlocked(this);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
Database::RecvClose()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!CloseInternal())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
bool
|
|
NormalTransaction::RecvDeleteMe()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
return PBackgroundIDBTransactionParent::Send__delete__(this);
|
|
}
|
|
|
|
bool
|
|
NormalTransaction::RecvCommit()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return TransactionBase::RecvCommit();
|
|
}
|
|
|
|
bool
|
|
NormalTransaction::RecvAbort(const nsresult& aResultCode)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return TransactionBase::RecvAbort(aResultCode);
|
|
}
|
|
|
|
PBackgroundIDBRequestParent*
|
|
NormalTransaction::AllocPBackgroundIDBRequestParent(
|
|
const RequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != RequestParams::T__None);
|
|
|
|
return AllocRequest(aParams, IsSameProcessActor());
|
|
}
|
|
|
|
bool
|
|
NormalTransaction::RecvPBackgroundIDBRequestConstructor(
|
|
PBackgroundIDBRequestParent* aActor,
|
|
const RequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(aParams.type() != RequestParams::T__None);
|
|
|
|
return StartRequest(aActor);
|
|
}
|
|
|
|
bool
|
|
NormalTransaction::DeallocPBackgroundIDBRequestParent(
|
|
PBackgroundIDBRequestParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
return DeallocRequest(aActor);
|
|
}
|
|
|
|
PBackgroundIDBCursorParent*
|
|
NormalTransaction::AllocPBackgroundIDBCursorParent(
|
|
const OpenCursorParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return AllocCursor(aParams, IsSameProcessActor());
|
|
}
|
|
|
|
bool
|
|
NormalTransaction::RecvPBackgroundIDBCursorConstructor(
|
|
PBackgroundIDBCursorParent* aActor,
|
|
const OpenCursorParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
|
|
|
|
return StartCursor(aActor, aParams);
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::RecvDeleteMe()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!IsActorDestroyed());
|
|
|
|
return PBackgroundIDBVersionChangeTransactionParent::Send__delete__(this);
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::RecvCommit()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return TransactionBase::RecvCommit();
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::RecvAbort(const nsresult& aResultCode)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return TransactionBase::RecvAbort(aResultCode);
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::RecvCreateObjectStore(
|
|
const ObjectStoreMetadata& aMetadata)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!aMetadata.id())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
|
|
MOZ_ASSERT(dbMetadata);
|
|
|
|
if (NS_WARN_IF(aMetadata.id() != dbMetadata->mNextObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
auto* foundMetadata =
|
|
MetadataNameOrIdMatcher<FullObjectStoreMetadata>::Match(
|
|
dbMetadata->mObjectStores, aMetadata.id(), aMetadata.name());
|
|
|
|
if (NS_WARN_IF(foundMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
dbMetadata->mNextObjectStoreId++;
|
|
|
|
RefPtr<CreateObjectStoreOp> op = new CreateObjectStoreOp(this, aMetadata);
|
|
|
|
if (NS_WARN_IF(!op->Init(this))) {
|
|
op->Cleanup();
|
|
return false;
|
|
}
|
|
|
|
op->DispatchToConnectionPool();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::RecvDeleteObjectStore(const int64_t& aObjectStoreId)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!aObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> foundMetadata =
|
|
GetMetadataForObjectStoreId(aObjectStoreId);
|
|
|
|
if (NS_WARN_IF(!foundMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
op->DispatchToConnectionPool();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::RecvRenameObjectStore(const int64_t& aObjectStoreId,
|
|
const nsString& aName)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!aObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> foundMetadata =
|
|
GetMetadataForObjectStoreId(aObjectStoreId);
|
|
|
|
if (NS_WARN_IF(!foundMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
foundMetadata->mCommonMetadata.name() = aName;
|
|
|
|
RefPtr<RenameObjectStoreOp> renameOp =
|
|
new RenameObjectStoreOp(this, foundMetadata);
|
|
|
|
if (NS_WARN_IF(!renameOp->Init(this))) {
|
|
renameOp->Cleanup();
|
|
return false;
|
|
}
|
|
|
|
renameOp->DispatchToConnectionPool();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::RecvCreateIndex(const int64_t& aObjectStoreId,
|
|
const IndexMetadata& aMetadata)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!aObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!aMetadata.id())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
const RefPtr<FullDatabaseMetadata> dbMetadata = GetDatabase()->Metadata();
|
|
MOZ_ASSERT(dbMetadata);
|
|
|
|
if (NS_WARN_IF(aMetadata.id() != dbMetadata->mNextIndexId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
|
|
GetMetadataForObjectStoreId(aObjectStoreId);
|
|
|
|
if (NS_WARN_IF(!foundObjectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
RefPtr<FullIndexMetadata> foundIndexMetadata =
|
|
MetadataNameOrIdMatcher<FullIndexMetadata>::Match(
|
|
foundObjectStoreMetadata->mIndexes, aMetadata.id(), aMetadata.name());
|
|
|
|
if (NS_WARN_IF(foundIndexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
RefPtr<FullIndexMetadata> newMetadata = new FullIndexMetadata();
|
|
newMetadata->mCommonMetadata = aMetadata;
|
|
|
|
if (NS_WARN_IF(!foundObjectStoreMetadata->mIndexes.Put(aMetadata.id(),
|
|
newMetadata,
|
|
fallible))) {
|
|
return false;
|
|
}
|
|
|
|
dbMetadata->mNextIndexId++;
|
|
|
|
RefPtr<CreateIndexOp> op =
|
|
new CreateIndexOp(this, aObjectStoreId, aMetadata);
|
|
|
|
if (NS_WARN_IF(!op->Init(this))) {
|
|
op->Cleanup();
|
|
return false;
|
|
}
|
|
|
|
op->DispatchToConnectionPool();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::RecvDeleteIndex(const int64_t& aObjectStoreId,
|
|
const int64_t& aIndexId)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!aObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!aIndexId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
if (NS_WARN_IF(aIndexId >= dbMetadata->mNextIndexId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
|
|
GetMetadataForObjectStoreId(aObjectStoreId);
|
|
|
|
if (NS_WARN_IF(!foundObjectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
RefPtr<FullIndexMetadata> foundIndexMetadata =
|
|
GetMetadataForIndexId(foundObjectStoreMetadata, aIndexId);
|
|
|
|
if (NS_WARN_IF(!foundIndexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
op->DispatchToConnectionPool();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::RecvRenameIndex(const int64_t& aObjectStoreId,
|
|
const int64_t& aIndexId,
|
|
const nsString& aName)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
if (NS_WARN_IF(!aObjectStoreId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!aIndexId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
if (NS_WARN_IF(aIndexId >= dbMetadata->mNextIndexId)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
RefPtr<FullObjectStoreMetadata> foundObjectStoreMetadata =
|
|
GetMetadataForObjectStoreId(aObjectStoreId);
|
|
|
|
if (NS_WARN_IF(!foundObjectStoreMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
RefPtr<FullIndexMetadata> foundIndexMetadata =
|
|
GetMetadataForIndexId(foundObjectStoreMetadata, aIndexId);
|
|
|
|
if (NS_WARN_IF(!foundIndexMetadata)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
foundIndexMetadata->mCommonMetadata.name() = aName;
|
|
|
|
RefPtr<RenameIndexOp> renameOp =
|
|
new RenameIndexOp(this, foundIndexMetadata, aObjectStoreId);
|
|
|
|
if (NS_WARN_IF(!renameOp->Init(this))) {
|
|
renameOp->Cleanup();
|
|
return false;
|
|
}
|
|
|
|
renameOp->DispatchToConnectionPool();
|
|
|
|
return true;
|
|
}
|
|
|
|
PBackgroundIDBRequestParent*
|
|
VersionChangeTransaction::AllocPBackgroundIDBRequestParent(
|
|
const RequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aParams.type() != RequestParams::T__None);
|
|
|
|
return AllocRequest(aParams, IsSameProcessActor());
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::RecvPBackgroundIDBRequestConstructor(
|
|
PBackgroundIDBRequestParent* aActor,
|
|
const RequestParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(aParams.type() != RequestParams::T__None);
|
|
|
|
return StartRequest(aActor);
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::DeallocPBackgroundIDBRequestParent(
|
|
PBackgroundIDBRequestParent* aActor)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
|
|
return DeallocRequest(aActor);
|
|
}
|
|
|
|
PBackgroundIDBCursorParent*
|
|
VersionChangeTransaction::AllocPBackgroundIDBCursorParent(
|
|
const OpenCursorParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
return AllocCursor(aParams, IsSameProcessActor());
|
|
}
|
|
|
|
bool
|
|
VersionChangeTransaction::RecvPBackgroundIDBCursorConstructor(
|
|
PBackgroundIDBCursorParent* aActor,
|
|
const OpenCursorParams& aParams)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(aActor);
|
|
MOZ_ASSERT(aParams.type() != OpenCursorParams::T__None);
|
|
|
|
return StartCursor(aActor, aParams);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool
|
|
Cursor::RecvDeleteMe()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
if (NS_WARN_IF(mCurrentlyRunningOp)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
return PBackgroundIDBCursorParent::Send__delete__(this);
|
|
}
|
|
|
|
bool
|
|
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 false;
|
|
}
|
|
|
|
if (NS_WARN_IF(mCurrentlyRunningOp)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(mTransaction->mCommitOrAbortReceived)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
RefPtr<ContinueOp> continueOp = new ContinueOp(this, aParams);
|
|
if (NS_WARN_IF(!continueOp->Init(mTransaction))) {
|
|
continueOp->Cleanup();
|
|
return false;
|
|
}
|
|
|
|
continueOp->DispatchToConnectionPool();
|
|
mCurrentlyRunningOp = continueOp;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* 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)
|
|
{ }
|
|
|
|
FileManager::~FileManager()
|
|
{ }
|
|
|
|
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)
|
|
{
|
|
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,
|
|
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!");
|
|
|
|
sInstance = this;
|
|
}
|
|
|
|
QuotaClient::~QuotaClient()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");
|
|
MOZ_ASSERT(!mMaintenanceThreadPool);
|
|
|
|
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,
|
|
const AtomicBool& aCanceled,
|
|
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 &&
|
|
!aCanceled) {
|
|
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 && !aCanceled;
|
|
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);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (aUsageInfo) {
|
|
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("Refusing to open databases for \"%s\" because "
|
|
"an unexpected file exists in the storage "
|
|
"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,
|
|
const AtomicBool& aCanceled,
|
|
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, aCanceled, 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;
|
|
|
|
// Shutdown maintenance thread pool (this spins the event loop until all
|
|
// threads are gone). This should release any maintenance related quota
|
|
// objects.
|
|
if (mMaintenanceThreadPool) {
|
|
mMaintenanceThreadPool->Shutdown();
|
|
mMaintenanceThreadPool = nullptr;
|
|
}
|
|
|
|
// Let any runnables dispatched from dying maintenance threads to be
|
|
// processed. This should release any maintenance related directory locks.
|
|
if (mCurrentMaintenance) {
|
|
nsIThread* currentThread = NS_GetCurrentThread();
|
|
MOZ_ASSERT(currentThread);
|
|
|
|
do {
|
|
MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
|
|
} while (!mCurrentMaintenance);
|
|
}
|
|
|
|
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,
|
|
const AtomicBool& aCanceled,
|
|
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 &&
|
|
!aCanceled) {
|
|
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, aCanceled, 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 (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
|
|
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 (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
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 (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
|
|
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 (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
|
|
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 (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
QuotaManager* quotaManager = QuotaManager::Get();
|
|
MOZ_ASSERT(quotaManager);
|
|
|
|
nsresult rv = quotaManager->EnsureStorageIsInitialized();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> storageDir = GetFileForPath(quotaManager->GetStoragePath());
|
|
if (NS_WARN_IF(!storageDir)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
bool exists;
|
|
rv = storageDir->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!exists) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
bool isDirectory;
|
|
rv = storageDir->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
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;
|
|
rv = storageDir->Clone(getter_AddRefs(persistenceDir));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = persistenceDir->Append(NS_ConvertASCIItoUTF16(persistenceTypeString));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = persistenceDir->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!exists) {
|
|
continue;
|
|
}
|
|
|
|
rv = persistenceDir->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!isDirectory)) {
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsISimpleEnumerator> persistenceDirEntries;
|
|
rv = persistenceDir->GetDirectoryEntries(
|
|
getter_AddRefs(persistenceDirEntries));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!persistenceDirEntries) {
|
|
continue;
|
|
}
|
|
|
|
while (true) {
|
|
// Loop over "<origin>/idb" directories.
|
|
if (IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
bool persistenceDirHasMoreEntries;
|
|
rv = persistenceDirEntries->HasMoreElements(
|
|
&persistenceDirHasMoreEntries);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!persistenceDirHasMoreEntries) {
|
|
break;
|
|
}
|
|
|
|
nsCOMPtr<nsISupports> persistenceDirEntry;
|
|
rv = persistenceDirEntries->GetNext(getter_AddRefs(persistenceDirEntry));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> originDir = do_QueryInterface(persistenceDirEntry);
|
|
MOZ_ASSERT(originDir);
|
|
|
|
rv = originDir->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(exists);
|
|
|
|
rv = originDir->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!isDirectory) {
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> idbDir;
|
|
rv = originDir->Clone(getter_AddRefs(idbDir));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = idbDir->Append(idbDirName);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
rv = idbDir->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!exists) {
|
|
continue;
|
|
}
|
|
|
|
rv = idbDir->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (NS_WARN_IF(!isDirectory)) {
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsISimpleEnumerator> idbDirEntries;
|
|
rv = idbDir->GetDirectoryEntries(getter_AddRefs(idbDirEntries));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!idbDirEntries) {
|
|
continue;
|
|
}
|
|
|
|
nsCString suffix;
|
|
nsCString group;
|
|
nsCString origin;
|
|
bool isApp;
|
|
nsTArray<nsString> databasePaths;
|
|
|
|
while (true) {
|
|
// Loop over files in the "idb" directory.
|
|
if (IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
bool idbDirHasMoreEntries;
|
|
rv = idbDirEntries->HasMoreElements(&idbDirHasMoreEntries);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!idbDirHasMoreEntries) {
|
|
break;
|
|
}
|
|
|
|
nsCOMPtr<nsISupports> idbDirEntry;
|
|
rv = idbDirEntries->GetNext(getter_AddRefs(idbDirEntry));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> idbDirFile = do_QueryInterface(idbDirEntry);
|
|
MOZ_ASSERT(idbDirFile);
|
|
|
|
nsString idbFilePath;
|
|
rv = idbDirFile->GetPath(idbFilePath);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!StringEndsWith(idbFilePath, sqliteExtension)) {
|
|
continue;
|
|
}
|
|
|
|
rv = idbDirFile->Exists(&exists);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
MOZ_ASSERT(exists);
|
|
|
|
rv = idbDirFile->IsDirectory(&isDirectory);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (isDirectory) {
|
|
continue;
|
|
}
|
|
|
|
// Found a database.
|
|
if (databasePaths.IsEmpty()) {
|
|
MOZ_ASSERT(suffix.IsEmpty());
|
|
MOZ_ASSERT(group.IsEmpty());
|
|
MOZ_ASSERT(origin.IsEmpty());
|
|
|
|
int64_t dummyTimeStamp;
|
|
if (NS_WARN_IF(NS_FAILED(
|
|
quotaManager->GetDirectoryMetadata2(originDir,
|
|
&dummyTimeStamp,
|
|
suffix,
|
|
group,
|
|
origin,
|
|
&isApp)))) {
|
|
// 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)));
|
|
|
|
nsCOMPtr<nsIFile> directory;
|
|
|
|
// Idle maintenance may occur before origin is initailized.
|
|
// Ensure origin is initialized first. It will initialize all origins
|
|
// for temporary storage including IDB origins.
|
|
rv = quotaManager->EnsureOriginIsInitialized(persistenceType,
|
|
suffix,
|
|
group,
|
|
origin,
|
|
isApp,
|
|
getter_AddRefs(directory));
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
};
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnBackgroundThread()) ||
|
|
IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
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);
|
|
|
|
if (NS_FAILED(mResultCode)) {
|
|
nsCString errorName;
|
|
GetErrorName(mResultCode, errorName);
|
|
|
|
IDB_WARNING("Maintenance finished with error: %s", errorName.get());
|
|
}
|
|
|
|
mDirectoryLock = nullptr;
|
|
|
|
// It can happen that we are only referenced by mCurrentMaintenance which is
|
|
// cleared in NoteFinishedMaintenance()
|
|
RefPtr<Maintenance> kungFuDeathGrip = this;
|
|
|
|
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) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = rv;
|
|
}
|
|
|
|
// 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))) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = rv;
|
|
}
|
|
|
|
mState = State::Finishing;
|
|
Finish();
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
Maintenance::DirectoryLockFailed()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mState == State::DirectoryOpenPending);
|
|
MOZ_ASSERT(!mDirectoryLock);
|
|
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = NS_ERROR_FAILURE;
|
|
}
|
|
|
|
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());
|
|
}
|
|
};
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
mMaintenance->IsAborted()) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIFile> databaseFile = GetFileForPath(mDatabasePath);
|
|
MOZ_ASSERT(databaseFile);
|
|
|
|
nsCOMPtr<mozIStorageConnection> connection;
|
|
nsresult rv = GetStorageConnection(databaseFile,
|
|
mPersistenceType,
|
|
mGroup,
|
|
mOrigin,
|
|
getter_AddRefs(connection));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
AutoClose autoClose(connection);
|
|
|
|
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;
|
|
}
|
|
|
|
MaintenanceAction maintenanceAction;
|
|
rv = DetermineMaintenanceAction(connection, databaseFile, &maintenanceAction);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
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);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
mMaintenance->IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
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);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
mMaintenance->IsAborted()) {
|
|
return NS_ERROR_ABORT;
|
|
}
|
|
|
|
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);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
mMaintenance->IsAborted()) {
|
|
return;
|
|
}
|
|
|
|
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);
|
|
|
|
if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonBackgroundThread()) ||
|
|
mMaintenance->IsAborted()) {
|
|
return;
|
|
}
|
|
|
|
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)
|
|
|
|
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(JS::StructuredCloneScope::DifferentProcess);
|
|
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;
|
|
}
|
|
|
|
// 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.AppendBytes(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.AppendBytes(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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
bool
|
|
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 true;
|
|
}
|
|
|
|
return BackgroundMutableFileParentBase::RecvPBackgroundFileHandleConstructor(
|
|
aActor, aMode);
|
|
}
|
|
|
|
bool
|
|
MutableFile::RecvGetFileId(int64_t* aFileId)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(mFileInfo);
|
|
|
|
if (NS_WARN_IF(!IndexedDatabaseManager::InTestingMode())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
*aFileId = mFileInfo->Id();
|
|
return true;
|
|
}
|
|
|
|
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) {
|
|
if (principalInfo.type() != PrincipalInfo::TContentPrincipalInfo) {
|
|
if (aContentParent) {
|
|
// We just want ContentPrincipalInfo or SystemPrincipalInfo.
|
|
aContentParent->KillHard("IndexedDB CheckPermission 0");
|
|
}
|
|
|
|
return NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR;
|
|
}
|
|
|
|
const ContentPrincipalInfo& contentPrincipalInfo =
|
|
principalInfo.get_ContentPrincipalInfo();
|
|
if (contentPrincipalInfo.attrs().mPrivateBrowsingId != 0) {
|
|
// IndexedDB is currently disabled in privateBrowsing.
|
|
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::IsOriginInternal(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)
|
|
|
|
// Run() assumes that the caller holds a strong reference to the object that
|
|
// can't be cleared while Run() is being executed.
|
|
// So if you call Run() directly (as opposed to dispatching to an event queue)
|
|
// you need to make sure there's such a reference.
|
|
// See bug 1356824 for more details.
|
|
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;
|
|
}
|
|
|
|
// The caller holds a strong reference to us, no need for a self reference
|
|
// before calling Run().
|
|
|
|
mState = State::SendingResults;
|
|
MOZ_ALWAYS_SUCCEEDS(Run());
|
|
|
|
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;
|
|
}
|
|
|
|
// The caller holds a strong reference to us, no need for a self reference
|
|
// before calling Run().
|
|
|
|
mState = State::SendingResults;
|
|
MOZ_ALWAYS_SUCCEEDS(Run());
|
|
}
|
|
|
|
void
|
|
FactoryOp::ActorDestroy(ActorDestroyReason aWhy)
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
|
|
NoteActorDestroyed();
|
|
}
|
|
|
|
bool
|
|
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 true;
|
|
}
|
|
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
|
|
#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,
|
|
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;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// We are being called with an assuption that mWaitingFactoryOp holds a strong
|
|
// reference to us.
|
|
RefPtr<OpenDatabaseOp> kungFuDeathGrip;
|
|
|
|
if (mMaybeBlockedDatabases.RemoveElement(aDatabase) &&
|
|
mMaybeBlockedDatabases.IsEmpty()) {
|
|
if (actorDestroyed) {
|
|
DatabaseActorInfo* info;
|
|
MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info));
|
|
MOZ_ASSERT(info->mWaitingFactoryOp == this);
|
|
kungFuDeathGrip =
|
|
static_cast<OpenDatabaseOp*>(info->mWaitingFactoryOp.get());
|
|
info->mWaitingFactoryOp = nullptr;
|
|
} else {
|
|
WaitForTransactions();
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = rv;
|
|
}
|
|
|
|
// A strong reference is held in kungFuDeathGrip, so it's safe to call Run()
|
|
// directly.
|
|
|
|
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();
|
|
|
|
DatabaseActorInfo* info;
|
|
if (gLiveDatabaseHashtable &&
|
|
gLiveDatabaseHashtable->Get(mDatabaseId, &info) &&
|
|
info->mWaitingFactoryOp) {
|
|
MOZ_ASSERT(info->mWaitingFactoryOp == this);
|
|
// SendResults() should only be called by Run() and Run() should only be
|
|
// called if there's a strong reference to the object that can't be cleared
|
|
// here, so it's safe to clear mWaitingFactoryOp without adding additional
|
|
// strong reference.
|
|
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,
|
|
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;
|
|
}
|
|
|
|
// We are being called with an assuption that mWaitingFactoryOp holds a strong
|
|
// reference to us.
|
|
RefPtr<OpenDatabaseOp> kungFuDeathGrip;
|
|
|
|
if (mMaybeBlockedDatabases.RemoveElement(aDatabase) &&
|
|
mMaybeBlockedDatabases.IsEmpty()) {
|
|
if (actorDestroyed) {
|
|
DatabaseActorInfo* info;
|
|
MOZ_ALWAYS_TRUE(gLiveDatabaseHashtable->Get(mDatabaseId, &info));
|
|
MOZ_ASSERT(info->mWaitingFactoryOp == this);
|
|
kungFuDeathGrip =
|
|
static_cast<OpenDatabaseOp*>(info->mWaitingFactoryOp.get());
|
|
info->mWaitingFactoryOp = nullptr;
|
|
} else {
|
|
WaitForTransactions();
|
|
}
|
|
}
|
|
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
mResultCode = rv;
|
|
}
|
|
|
|
// A strong reference is held in kungFuDeathGrip, so it's safe to call Run()
|
|
// directly.
|
|
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// We hold a strong ref to the deleteOp, so it's safe to call Run() directly.
|
|
|
|
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;
|
|
|
|
// This TransactionDatabaseOperationBase can only be held alive by the IPDL.
|
|
// Run() can end up with clearing that last reference. So we need to add
|
|
// a self reference here.
|
|
RefPtr<TransactionDatabaseOperationBase> kungFuDeathGrip = this;
|
|
|
|
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);
|
|
|
|
if (NS_WARN_IF(IsActorDestroyed())) {
|
|
// Normally we wouldn't need to send any notifications if the actor was
|
|
// already destroyed, but this can be a VersionChangeOp which needs to
|
|
// notify its parent operation (OpenDatabaseOp) about the failure.
|
|
// So SendFailureResult needs to be called even when the actor was
|
|
// destroyed. Normal operations redundantly check if the actor was
|
|
// destroyed in SendSuccessResult and SendFailureResult, therefore it's
|
|
// ok to call it in all cases here.
|
|
if (NS_SUCCEEDED(mResultCode)) {
|
|
IDB_REPORT_INTERNAL_ERR();
|
|
mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
|
|
}
|
|
} else 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;
|
|
}
|
|
|
|
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(JS::StructuredCloneScope::DifferentProcess);
|
|
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();
|
|
}
|
|
|
|
bool
|
|
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 true;
|
|
}
|
|
|
|
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.Start();
|
|
MOZ_ALWAYS_TRUE(cloneData.Advance(iter, cloneInfo.offsetToKeyProp()));
|
|
MOZ_ALWAYS_TRUE(cloneData.UpdateBytes(iter, keyPropBuffer, keyPropSize));
|
|
}
|
|
}
|
|
|
|
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.Start();
|
|
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;
|
|
|
|
mData.Advance(mIter, 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(JS::StructuredCloneScope::DifferentProcess);
|
|
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(JS::StructuredCloneScope::DifferentProcess);
|
|
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;
|
|
if (mCursor->IsLocaleAware()) {
|
|
range.lower().ToLocaleBasedKey(*aKey, mCursor->mLocale);
|
|
}
|
|
} else {
|
|
*aKey = aLowerBound ? range.lower() : range.upper();
|
|
*aOpen = aLowerBound ? range.lowerOpen() : range.upperOpen();
|
|
if (mCursor->IsLocaleAware()) {
|
|
if (aLowerBound) {
|
|
range.lower().ToLocaleBasedKey(*aKey, mCursor->mLocale);
|
|
} else {
|
|
range.upper().ToLocaleBasedKey(*aKey, mCursor->mLocale);
|
|
}
|
|
}
|
|
}
|
|
} 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
|
|
}
|
|
|
|
bool
|
|
Utils::RecvDeleteMe()
|
|
{
|
|
AssertIsOnBackgroundThread();
|
|
MOZ_ASSERT(!mActorDestroyed);
|
|
|
|
return PBackgroundIndexedDBUtilsParent::Send__delete__(this);
|
|
}
|
|
|
|
bool
|
|
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 false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!IndexedDatabaseManager::InTestingMode())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(aPersistenceType != quota::PERSISTENCE_TYPE_PERSISTENT &&
|
|
aPersistenceType != quota::PERSISTENCE_TYPE_TEMPORARY &&
|
|
aPersistenceType != quota::PERSISTENCE_TYPE_DEFAULT)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(aOrigin.IsEmpty())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(aDatabaseName.IsEmpty())) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(aFileId == 0)) {
|
|
ASSERT_UNLESS_FUZZING();
|
|
return false;
|
|
}
|
|
|
|
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 false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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
|