dom/indexedDB/ActorsParent.cpp
author Lars T Hansen <lhansen@mozilla.com>
Mon, 18 Jan 2016 09:48:23 +0100
changeset 280790 85f60fdd98facbcc80fa7edb22eca55ff534d68b
parent 278413 84d9061974399c362ea07a0c108e616724e445a0
child 281338 38ef259dd1f1f795724f519f5847c07b65afbaeb
permissions -rw-r--r--
Bug 1239666 - part 2, dom/indexedDB change. r=khuey

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "ActorsParent.h"

#include <algorithm>
#include "FileInfo.h"
#include "FileManager.h"
#include "IDBObjectStore.h"
#include "IDBTransaction.h"
#include "IndexedDatabase.h"
#include "IndexedDatabaseInlines.h"
#include "IndexedDatabaseManager.h"
#include "js/StructuredClone.h"
#include "js/Value.h"
#include "jsapi.h"
#include "KeyPath.h"
#include "mozilla/Attributes.h"
#include "mozilla/AppProcessChecker.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/Endian.h"
#include "mozilla/LazyIdleThread.h"
#include "mozilla/Maybe.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/storage.h"
#include "mozilla/unused.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/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/QuotaManager.h"
#include "mozilla/dom/quota/UsageInfo.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/InputStreamParams.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/ipc/PBackground.h"
#include "mozilla/storage/Variant.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsClassHashtable.h"
#include "nsCOMPtr.h"
#include "nsDataHashtable.h"
#include "nsEscape.h"
#include "nsHashKeys.h"
#include "nsNetUtil.h"
#include "nsISimpleEnumerator.h"
#include "nsIAppsService.h"
#include "nsIEventTarget.h"
#include "nsIFile.h"
#include "nsIFileURL.h"
#include "nsIInputStream.h"
#include "nsIInterfaceRequestor.h"
#include "nsInterfaceHashtable.h"
#include "nsIOutputStream.h"
#include "nsIPrincipal.h"
#include "nsIScriptSecurityManager.h"
#include "nsISupports.h"
#include "nsISupportsImpl.h"
#include "nsISupportsPriority.h"
#include "nsIThread.h"
#include "nsITimer.h"
#include "nsIURI.h"
#include "nsNetUtil.h"
#include "nsPrintfCString.h"
#include "nsQueryObject.h"
#include "nsRefPtrHashtable.h"
#include "nsString.h"
#include "nsThreadPool.h"
#include "nsThreadUtils.h"
#include "nsXPCOMCID.h"
#include "PermissionRequestBase.h"
#include "ProfilerHelpers.h"
#include "prsystem.h"
#include "prtime.h"
#include "ReportInternalError.h"
#include "snappy/snappy.h"

#define DISABLE_ASSERTS_FOR_FUZZING 0

#if DISABLE_ASSERTS_FOR_FUZZING
#define ASSERT_UNLESS_FUZZING(...) do { } while (0)
#else
#define ASSERT_UNLESS_FUZZING(...) MOZ_ASSERT(false, __VA_ARGS__)
#endif

#define IDB_DEBUG_LOG(_args)                                                   \
  MOZ_LOG(IndexedDatabaseManager::GetLoggingModule(),                           \
         LogLevel::Debug,                                                         \
         _args )

#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
#define IDB_MOBILE
#endif

#define BLOB_IMPL_STORED_FILE_IID \
  {0x6b505c84, 0x2c60, 0x4ffb, {0x8b, 0x91, 0xfe, 0x22, 0xb1, 0xec, 0x75, 0xe2}}

namespace mozilla {
namespace dom {
namespace indexedDB {

using namespace mozilla::dom::quota;
using namespace mozilla::ipc;

namespace {

class ConnectionPool;
class Cursor;
class Database;
struct DatabaseActorInfo;
class DatabaseLoggingInfo;
class DatabaseFile;
class Factory;
class 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 == 6,
              "Need to update the major schema version.");

// Major schema version. Bump for almost everything.
const uint32_t kMajorSchemaVersion = 22;

// Minor schema version. Should almost always be 0 (maybe bump on release
// branches if we have to).
const uint32_t kMinorSchemaVersion = 0;

// The schema version we store in the SQLite database is a (signed) 32-bit
// integer. The major version is left-shifted 4 bits so the max value is
// 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF.
static_assert(kMajorSchemaVersion <= 0xFFFFFFF,
              "Major version needs to fit in 28 bits.");
static_assert(kMinorSchemaVersion <= 0xF,
              "Minor version needs to fit in 4 bits.");

const int32_t kSQLiteSchemaVersion =
  int32_t((kMajorSchemaVersion << 4) + kMinorSchemaVersion);

const int32_t kStorageProgressGranularity = 1000;

// Changing the value here will override the page size of new databases only.
// A journal mode change and VACUUM are needed to change existing databases, so
// the best way to do that is to use the schema version upgrade mechanism.
const uint32_t kSQLitePageSizeOverride =
#ifdef IDB_MOBILE
  2048;
#else
  4096;
#endif

static_assert(kSQLitePageSizeOverride == /* mozStorage default */ 0 ||
              (kSQLitePageSizeOverride % 2 == 0 &&
               kSQLitePageSizeOverride >= 512  &&
               kSQLitePageSizeOverride <= 65536),
              "Must be 0 (disabled) or a power of 2 between 512 and 65536!");

// Set to -1 to use SQLite's default, 0 to disable, or some positive number to
// enforce a custom limit.
const int32_t kMaxWALPages = 5000; // 20MB on desktop, 10MB on mobile.

// Set to some multiple of the page size to grow the database in larger chunks.
const uint32_t kSQLiteGrowthIncrement = kSQLitePageSizeOverride * 2;

static_assert(kSQLiteGrowthIncrement >= 0 &&
              kSQLiteGrowthIncrement % kSQLitePageSizeOverride == 0 &&
              kSQLiteGrowthIncrement < uint32_t(INT32_MAX),
              "Must be 0 (disabled) or a positive multiple of the page size!");

// The maximum number of threads that can be used for database activity at a
// single time.
const uint32_t kMaxConnectionThreadCount = 20;

static_assert(kMaxConnectionThreadCount, "Must have at least one thread!");

// The maximum number of threads to keep when idle. Threads that become idle in
// excess of this number will be shut down immediately.
const uint32_t kMaxIdleConnectionThreadCount = 2;

static_assert(kMaxConnectionThreadCount >= kMaxIdleConnectionThreadCount,
              "Idle thread limit must be less than total thread limit!");

// The length of time that database connections will be held open after all
// transactions have completed before doing idle maintenance.
const uint32_t kConnectionIdleMaintenanceMS = 2 * 1000; // 2 seconds

// The length of time that database connections will be held open after all
// transactions and maintenance have completed.
const uint32_t kConnectionIdleCloseMS = 10 * 1000; // 10 seconds

// The length of time that idle threads will stay alive before being shut down.
const uint32_t kConnectionThreadIdleMS = 30 * 1000; // 30 seconds

#define SAVEPOINT_CLAUSE "SAVEPOINT sp;"

const uint32_t kFileCopyBufferSize = 32768;

#define JOURNAL_DIRECTORY_NAME "journals"

const char kFileManagerDirectoryNameSuffix[] = ".files";
const char kSQLiteJournalSuffix[] = ".sqlite-journal";
const char kSQLiteSHMSuffix[] = ".sqlite-shm";
const char kSQLiteWALSuffix[] = ".sqlite-wal";

const char kPrefIndexedDBEnabled[] = "dom.indexedDB.enabled";

const char kPrefFileHandleEnabled[] = "dom.fileHandle.enabled";

#define IDB_PREFIX "indexedDB"

#define PERMISSION_STRING_CHROME_BASE IDB_PREFIX "-chrome-"
#define PERMISSION_STRING_CHROME_READ_SUFFIX "-read"
#define PERMISSION_STRING_CHROME_WRITE_SUFFIX "-write"

#ifdef DEBUG

const int32_t kDEBUGThreadPriority = nsISupportsPriority::PRIORITY_NORMAL;
const uint32_t kDEBUGThreadSleepMS = 0;

const int32_t kDEBUGTransactionThreadPriority =
  nsISupportsPriority::PRIORITY_NORMAL;
const uint32_t kDEBUGTransactionThreadSleepMS = 0;

#endif

struct FreeDeleter
{
  void
  operator()(void* aPtr) const
  {
    free(aPtr);
  }
};

template <typename T>
using UniqueFreePtr = UniquePtr<T, FreeDeleter>;

template <size_t N>
MOZ_CONSTEXPR size_t
LiteralStringLength(const char (&aArr)[N])
{
  static_assert(N, "Zero-length string literal?!");

  // Don't include the null terminator.
  return N - 1;
}

/*******************************************************************************
 * Metadata classes
 ******************************************************************************/

struct FullIndexMetadata
{
  IndexMetadata mCommonMetadata;

  bool mDeleted;

public:
  FullIndexMetadata()
    : mCommonMetadata(0, nsString(), KeyPath(0), 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 mComittedAutoIncrementId;

  bool mDeleted;

public:
  FullObjectStoreMetadata()
    : mCommonMetadata(0, nsString(), KeyPath(0), false)
    , mNextAutoIncrementId(0)
    , mComittedAutoIncrementId(0)
    , mDeleted(false)
  {
    // This can happen either on the QuotaManager IO thread or on a
    // versionchange transaction thread. These threads can never race so this is
    // totally safe.
  }

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FullObjectStoreMetadata);

  bool
  HasLiveIndexes() const;

private:
  ~FullObjectStoreMetadata()
  { }
};

typedef nsRefPtrHashtable<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());

    // Don't let |infoLength| overflow.
    if (NS_WARN_IF(UINT32_MAX - keyBuffer.Length() <
                   CompressedByteCountForIndexId(info.mIndexId) +
                   CompressedByteCountForNumber(keyBufferLength) +
                   CompressedByteCountForNumber(sortKeyBufferLength))) {
      IDB_REPORT_INTERNAL_ERR();
      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
    }

    const uint32_t infoLength =
      CompressedByteCountForIndexId(info.mIndexId) +
      CompressedByteCountForNumber(keyBufferLength) +
      CompressedByteCountForNumber(sortKeyBufferLength) +
      keyBufferLength +
      sortKeyBufferLength;

    // Don't let |blobDataLength| overflow.
    if (NS_WARN_IF(UINT32_MAX - infoLength < blobDataLength)) {
      IDB_REPORT_INTERNAL_ERR();
      return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
    }

    blobDataLength += infoLength;
  }

  UniqueFreePtr<uint8_t> blobData(
    static_cast<uint8_t*>(malloc(blobDataLength)));
  if (NS_WARN_IF(!blobData)) {
    IDB_REPORT_INTERNAL_ERR();
    return NS_ERROR_OUT_OF_MEMORY;
  }

  uint8_t* blobDataIter = blobData.get();

  for (uint32_t arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
    const IndexDataValue& info = aIndexValues[arrayIndex];
    const nsCString& keyBuffer = info.mKey.GetBuffer();
    const nsCString& sortKeyBuffer = info.mSortKey.GetBuffer();
    const uint32_t keyBufferLength = keyBuffer.Length();
    const uint32_t sortKeyBufferLength = sortKeyBuffer.Length();

    WriteCompressedIndexId(info.mIndexId, info.mUnique, &blobDataIter);
    WriteCompressedNumber(keyBufferLength, &blobDataIter);

    memcpy(blobDataIter, keyBuffer.get(), keyBufferLength);
    blobDataIter += keyBufferLength;

    WriteCompressedNumber(sortKeyBufferLength, &blobDataIter);

    memcpy(blobDataIter, sortKeyBuffer.get(), sortKeyBufferLength);
    blobDataIter += sortKeyBufferLength;
  }

  MOZ_ASSERT(blobDataIter == blobData.get() + blobDataLength);

  aCompressedIndexDataValues.swap(blobData);
  *aCompressedIndexDataValuesLength = uint32_t(blobDataLength);

  return NS_OK;
}

nsresult
ReadCompressedIndexDataValuesFromBlob(
                                   const uint8_t* aBlobData,
                                   uint32_t aBlobDataLength,
                                   FallibleTArray<IndexDataValue>& aIndexValues)
{
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_ASSERT(!IsOnBackgroundThread());
  MOZ_ASSERT(aBlobData);
  MOZ_ASSERT(aBlobDataLength);
  MOZ_ASSERT(aIndexValues.IsEmpty());

  PROFILER_LABEL("IndexedDB",
                 "ReadCompressedIndexDataValuesFromBlob",
                 js::ProfileEntry::Category::STORAGE);

  const uint8_t* blobDataIter = aBlobData;
  const uint8_t* blobDataEnd = aBlobData + aBlobDataLength;

  while (blobDataIter < blobDataEnd) {
    int64_t indexId;
    bool unique;
    ReadCompressedIndexId(&blobDataIter, blobDataEnd, &indexId, &unique);

    if (NS_WARN_IF(blobDataIter == blobDataEnd)) {
      IDB_REPORT_INTERNAL_ERR();
      return NS_ERROR_FILE_CORRUPTED;
    }

    // Read key buffer length.
    const uint64_t keyBufferLength =
      ReadCompressedNumber(&blobDataIter, blobDataEnd);

    if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
        NS_WARN_IF(keyBufferLength > uint64_t(UINT32_MAX)) ||
        NS_WARN_IF(blobDataIter + keyBufferLength > blobDataEnd)) {
      IDB_REPORT_INTERNAL_ERR();
      return NS_ERROR_FILE_CORRUPTED;
    }

    nsCString keyBuffer(reinterpret_cast<const char*>(blobDataIter),
                        uint32_t(keyBufferLength));
    blobDataIter += keyBufferLength;

    IndexDataValue idv(indexId, unique, Key(keyBuffer));

    // Read sort key buffer length.
    const uint64_t sortKeyBufferLength =
      ReadCompressedNumber(&blobDataIter, blobDataEnd);

    if (sortKeyBufferLength > 0) {
      if (NS_WARN_IF(blobDataIter == blobDataEnd) ||
          NS_WARN_IF(sortKeyBufferLength > uint64_t(UINT32_MAX)) ||
          NS_WARN_IF(blobDataIter + sortKeyBufferLength > blobDataEnd)) {
        IDB_REPORT_INTERNAL_ERR();
        return NS_ERROR_FILE_CORRUPTED;
      }

      nsCString sortKeyBuffer(reinterpret_cast<const char*>(blobDataIter),
                              uint32_t(sortKeyBufferLength));
      blobDataIter += sortKeyBufferLength;

      idv.mSortKey = Key(sortKeyBuffer);
    }

    if (NS_WARN_IF(!aIndexValues.InsertElementSorted(idv, fallible))) {
      IDB_REPORT_INTERNAL_ERR();
      return NS_ERROR_OUT_OF_MEMORY;
    }
  }

  MOZ_ASSERT(blobDataIter == blobDataEnd);

  return NS_OK;
}

// static
template <typename T>
nsresult
ReadCompressedIndexDataValuesFromSource(
                                   T* aSource,
                                   uint32_t aColumnIndex,
                                   FallibleTArray<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,
                              FallibleTArray<IndexDataValue>& aIndexValues)
{
  return ReadCompressedIndexDataValuesFromSource(aStatement,
                                                 aColumnIndex,
                                                 aIndexValues);
}

nsresult
ReadCompressedIndexDataValues(mozIStorageValueArray* aValues,
                              uint32_t aColumnIndex,
                              FallibleTArray<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`
  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_TRUE(NS_SUCCEEDED(aValues->GetNumEntries(&argCount)));
    MOZ_ASSERT(argCount == 4);

    int32_t valueType;
    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(0, &valueType)));
    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL ||
               valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);

    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(1, &valueType)));
    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);

    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(2, &valueType)));
    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER);

    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(3, &valueType)));
    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
  }
#endif

  // Read out the previous value. It may be NULL, in which case we'll just end
  // up with an empty array.
  AutoFallibleTArray<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 MOZ_CONSTEXPR_VAR uint8_t kOldNumberTag = 0x1;
  static MOZ_CONSTEXPR_VAR uint8_t kOldDateTag = 0x2;
  static MOZ_CONSTEXPR_VAR uint8_t kOldStringTag = 0x3;
  static MOZ_CONSTEXPR_VAR uint8_t kOldArrayTag = 0x4;
  static MOZ_CONSTEXPR_VAR uint8_t kOldMaxType = kOldArrayTag;

  if (NS_WARN_IF(aRecursionDepth > Key::kMaxRecursionDepth)) {
    IDB_REPORT_INTERNAL_ERR();
    return NS_ERROR_FILE_CORRUPTED;
  }

  const uint8_t sourceTag = *aSource - (aTagOffset * kOldMaxType);
  MOZ_ASSERT(sourceTag);

  if (NS_WARN_IF(sourceTag > kOldMaxType * Key::kMaxArrayCollapse)) {
    IDB_REPORT_INTERNAL_ERR();
    return NS_ERROR_FILE_CORRUPTED;
  }

  if (sourceTag == kOldNumberTag || sourceTag == kOldDateTag) {
    // Write the new tag.
    *aDestination++ =
      (sourceTag == kOldNumberTag ? Key::eFloat : Key::eDate) +
      (aTagOffset * Key::eMaxType);
    aSource++;

    // Numbers and Dates are encoded as 64-bit integers, but trailing 0
    // bytes have been removed.
    const uint32_t byteCount =
      AdjustedSize(sizeof(uint64_t), aSource, aSourceEnd);

    for (uint32_t count = 0; count < byteCount; count++) {
      *aDestination++ = *aSource++;
    }

    return NS_OK;
  }

  if (sourceTag == kOldStringTag) {
    // Write the new tag.
    *aDestination++ = Key::eString + (aTagOffset * Key::eMaxType);
    aSource++;

    while (aSource < aSourceEnd) {
      const uint8_t byte = *aSource++;
      *aDestination++ = byte;

      if (!byte) {
        // Just copied the terminator.
        break;
      }

      // Maybe copy one or two extra bytes if the byte is tagged and we have
      // enough source space.
      if (byte & 0x80) {
        const uint32_t byteCount =
          AdjustedSize((byte & 0x40) ? 2 : 1, aSource, aSourceEnd);

        for (uint32_t count = 0; count < byteCount; count++) {
          *aDestination++ = *aSource++;
        }
      }
    }

    return NS_OK;
  }

  if (NS_WARN_IF(sourceTag < kOldArrayTag)) {
    IDB_REPORT_INTERNAL_ERR();
    return NS_ERROR_FILE_CORRUPTED;
  }

  aTagOffset++;

  if (aTagOffset == Key::kMaxArrayCollapse) {
    MOZ_ASSERT(sourceTag == kOldArrayTag);

    *aDestination++ = (aTagOffset * Key::eMaxType);
    aSource++;

    aTagOffset = 0;
  }

  while (aSource < aSourceEnd &&
         (*aSource - (aTagOffset * kOldMaxType)) != Key::eTerminator) {
    nsresult rv = CopyAndUpgradeKeyBufferInternal(aSource,
                                                  aSourceEnd,
                                                  aDestination,
                                                  aTagOffset,
                                                  aRecursionDepth + 1);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    aTagOffset = 0;
  }

  if (aSource < aSourceEnd) {
    MOZ_ASSERT((*aSource - (aTagOffset * kOldMaxType)) == Key::eTerminator);
    *aDestination++ = Key::eTerminator + (aTagOffset * Key::eMaxType);
    aSource++;
  }

  return NS_OK;
}

NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction,
                  mozIStorageFunction);

NS_IMETHODIMP
UpgradeSchemaFrom17_0To18_0Helper::
UpgradeKeyFunction::OnFunctionCall(mozIStorageValueArray* aValues,
                                   nsIVariant** _retval)
{
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_ASSERT(!IsOnBackgroundThread());
  MOZ_ASSERT(aValues);
  MOZ_ASSERT(_retval);

#ifdef DEBUG
  {
    uint32_t argCount;
    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetNumEntries(&argCount)));
    MOZ_ASSERT(argCount == 1);

    int32_t valueType;
    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(aValues->GetTypeOfIndex(0, &valueType)));
    MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB);
  }
#endif

  // Dig the old key out of the values.
  const uint8_t* blobData;
  uint32_t blobDataLength;
  nsresult rv = aValues->GetSharedBlob(0, &blobDataLength, &blobData);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Upgrading the key doesn't change the amount of space needed to hold it.
  UniqueFreePtr<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);

  upgradedBlobData.release();

  result.forget(_retval);
  return NS_OK;
}

// static
nsresult
UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(mozIStorageConnection* aConnection,
                                             const nsACString& aOrigin)
{
  MOZ_ASSERT(aConnection);
  MOZ_ASSERT(!aOrigin.IsEmpty());

  // Register the |upgrade_key| function.
  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_TRUE(NS_SUCCEEDED(
      aConnection->RemoveFunction(upgradeKeyFunctionName)));
    return rv;
  }

  rv = DoUpgradeInternal(aConnection, aOrigin);

  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
    aConnection->RemoveFunction(upgradeKeyFunctionName)));
  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
    aConnection->RemoveFunction(insertIDVFunctionName)));

  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_OK;
}

// static
nsresult
UpgradeSchemaFrom17_0To18_0Helper::DoUpgradeInternal(
                                             mozIStorageConnection* aConnection,
                                             const nsACString& aOrigin)
{
  MOZ_ASSERT(aConnection);
  MOZ_ASSERT(!aOrigin.IsEmpty());

  nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // Drop these triggers to avoid unnecessary work during the upgrade process.
    "DROP TRIGGER object_data_insert_trigger;"
    "DROP TRIGGER object_data_update_trigger;"
    "DROP TRIGGER object_data_delete_trigger;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // Drop these indexes before we do anything else to free disk space.
    "DROP INDEX index_data_object_data_id_index;"
    "DROP INDEX unique_index_data_object_data_id_index;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Create the new tables and triggers first.

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // This will eventually become the |database| table.
    "CREATE TABLE database_upgrade "
      "( name TEXT PRIMARY KEY"
      ", origin TEXT NOT NULL"
      ", version INTEGER NOT NULL DEFAULT 0"
      ", last_vacuum_time INTEGER NOT NULL DEFAULT 0"
      ", last_analyze_time INTEGER NOT NULL DEFAULT 0"
      ", last_vacuum_size INTEGER NOT NULL DEFAULT 0"
      ") WITHOUT ROWID;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
     // This will eventually become the |object_store| table.
    "CREATE TABLE object_store_upgrade"
      "( id INTEGER PRIMARY KEY"
      ", auto_increment INTEGER NOT NULL DEFAULT 0"
      ", name TEXT NOT NULL"
      ", key_path TEXT"
      ");"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // This will eventually become the |object_store_index| table.
    "CREATE TABLE object_store_index_upgrade"
      "( id INTEGER PRIMARY KEY"
      ", object_store_id INTEGER NOT NULL"
      ", name TEXT NOT NULL"
      ", key_path TEXT NOT NULL"
      ", unique_index INTEGER NOT NULL"
      ", multientry INTEGER NOT NULL"
      ", FOREIGN KEY (object_store_id) "
          "REFERENCES object_store(id) "
      ");"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // This will eventually become the |object_data| table.
    "CREATE TABLE object_data_upgrade"
      "( object_store_id INTEGER NOT NULL"
      ", key BLOB NOT NULL"
      ", index_data_values BLOB DEFAULT NULL"
      ", file_ids TEXT"
      ", data BLOB NOT NULL"
      ", PRIMARY KEY (object_store_id, key)"
      ", FOREIGN KEY (object_store_id) "
          "REFERENCES object_store(id) "
      ") WITHOUT ROWID;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // This will eventually become the |index_data| table.
    "CREATE TABLE index_data_upgrade"
      "( index_id INTEGER NOT NULL"
      ", value BLOB NOT NULL"
      ", object_data_key BLOB NOT NULL"
      ", object_store_id INTEGER NOT NULL"
      ", PRIMARY KEY (index_id, value, object_data_key)"
      ", FOREIGN KEY (index_id) "
          "REFERENCES object_store_index(id) "
      ", FOREIGN KEY (object_store_id, object_data_key) "
          "REFERENCES object_data(object_store_id, key) "
      ") WITHOUT ROWID;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // This will eventually become the |unique_index_data| table.
    "CREATE TABLE unique_index_data_upgrade"
      "( index_id INTEGER NOT NULL"
      ", value BLOB NOT NULL"
      ", object_store_id INTEGER NOT NULL"
      ", object_data_key BLOB NOT NULL"
      ", PRIMARY KEY (index_id, value)"
      ", FOREIGN KEY (index_id) "
          "REFERENCES object_store_index(id) "
      ", FOREIGN KEY (object_store_id, object_data_key) "
          "REFERENCES object_data(object_store_id, key) "
      ") WITHOUT ROWID;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // Temporarily store |index_data_values| that we build during the upgrade of
    // the index tables. We will later move this to the |object_data| table.
    "CREATE TEMPORARY TABLE temp_index_data_values "
      "( object_store_id INTEGER NOT NULL"
      ", key BLOB NOT NULL"
      ", index_data_values BLOB DEFAULT NULL"
      ", PRIMARY KEY (object_store_id, key)"
      ") WITHOUT ROWID;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // These two triggers help build the |index_data_values| blobs. The nested
    // SELECT statements help us achieve an "INSERT OR UPDATE"-like behavior.
    "CREATE TEMPORARY TRIGGER unique_index_data_upgrade_insert_trigger "
      "AFTER INSERT ON unique_index_data_upgrade "
      "BEGIN "
        "INSERT OR REPLACE INTO temp_index_data_values "
          "VALUES "
          "( NEW.object_store_id"
          ", NEW.object_data_key"
          ", insert_idv("
              "( SELECT index_data_values "
                  "FROM temp_index_data_values "
                  "WHERE object_store_id = NEW.object_store_id "
                  "AND key = NEW.object_data_key "
              "), NEW.index_id"
               ", 1" /* unique */
               ", NEW.value"
            ")"
          ");"
      "END;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "CREATE TEMPORARY TRIGGER index_data_upgrade_insert_trigger "
      "AFTER INSERT ON index_data_upgrade "
      "BEGIN "
        "INSERT OR REPLACE INTO temp_index_data_values "
          "VALUES "
          "( NEW.object_store_id"
          ", NEW.object_data_key"
          ", insert_idv("
              "("
                "SELECT index_data_values "
                  "FROM temp_index_data_values "
                  "WHERE object_store_id = NEW.object_store_id "
                  "AND key = NEW.object_data_key "
              "), NEW.index_id"
               ", 0" /* not unique */
               ", NEW.value"
            ")"
          ");"
      "END;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Update the |unique_index_data| table to change the column order, remove the
  // ON DELETE CASCADE clauses, and to apply the WITHOUT ROWID optimization.
  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // Insert all the data.
    "INSERT INTO unique_index_data_upgrade "
      "SELECT "
        "unique_index_data.index_id, "
        "upgrade_key(unique_index_data.value), "
        "object_data.object_store_id, "
        "upgrade_key(unique_index_data.object_data_key) "
        "FROM unique_index_data "
        "JOIN object_data "
        "ON unique_index_data.object_data_id = object_data.id;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // The trigger is no longer needed.
    "DROP TRIGGER unique_index_data_upgrade_insert_trigger;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // The old table is no longer needed.
    "DROP TABLE unique_index_data;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // Rename the table.
    "ALTER TABLE unique_index_data_upgrade "
      "RENAME TO unique_index_data;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Update the |index_data| table to change the column order, remove the ON
  // DELETE CASCADE clauses, and to apply the WITHOUT ROWID optimization.
  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // Insert all the data.
    "INSERT INTO index_data_upgrade "
      "SELECT "
        "index_data.index_id, "
        "upgrade_key(index_data.value), "
        "upgrade_key(index_data.object_data_key), "
        "object_data.object_store_id "
        "FROM index_data "
        "JOIN object_data "
        "ON index_data.object_data_id = object_data.id;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // The trigger is no longer needed.
    "DROP TRIGGER index_data_upgrade_insert_trigger;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // The old table is no longer needed.
    "DROP TABLE index_data;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // Rename the table.
    "ALTER TABLE index_data_upgrade "
      "RENAME TO index_data;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Update the |object_data| table to add the |index_data_values| column,
  // remove the ON DELETE CASCADE clause, and apply the WITHOUT ROWID
  // optimization.
  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // Insert all the data.
    "INSERT INTO object_data_upgrade "
      "SELECT "
        "object_data.object_store_id, "
        "upgrade_key(object_data.key_value), "
        "temp_index_data_values.index_data_values, "
        "object_data.file_ids, "
        "object_data.data "
        "FROM object_data "
        "LEFT JOIN temp_index_data_values "
        "ON object_data.object_store_id = "
          "temp_index_data_values.object_store_id "
        "AND upgrade_key(object_data.key_value) = "
          "temp_index_data_values.key;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // The temporary table is no longer needed.
    "DROP TABLE temp_index_data_values;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // The old table is no longer needed.
    "DROP TABLE object_data;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    // Rename the table.
    "ALTER TABLE object_data_upgrade "
      "RENAME TO object_data;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Update the |object_store_index| table to remove the UNIQUE constraint and
  // the ON DELETE CASCADE clause.
  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "INSERT INTO object_store_index_upgrade "
      "SELECT * "
        "FROM object_store_index;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "DROP TABLE object_store_index;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "ALTER TABLE object_store_index_upgrade "
      "RENAME TO object_store_index;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Update the |object_store| table to remove the UNIQUE constraint.
  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "INSERT INTO object_store_upgrade "
      "SELECT * "
        "FROM object_store;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "DROP TABLE object_store;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "ALTER TABLE object_store_upgrade "
      "RENAME TO object_store;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Update the |database| table to include the origin, vacuum information, and
  // apply the WITHOUT ROWID optimization.
  nsCOMPtr<mozIStorageStatement> stmt;
  rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
    "INSERT INTO database_upgrade "
      "SELECT name, :origin, version, 0, 0, 0 "
        "FROM database;"
  ), getter_AddRefs(stmt));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), aOrigin);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = stmt->Execute();
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "DROP TABLE database;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "ALTER TABLE database_upgrade "
      "RENAME TO database;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

#ifdef DEBUG
  {
    // Make sure there's only one entry in the |database| table.
    nsCOMPtr<mozIStorageStatement> stmt;
    MOZ_ASSERT(NS_SUCCEEDED(
      aConnection->CreateStatement(
        NS_LITERAL_CSTRING("SELECT COUNT(*) "
                             "FROM database;"),
        getter_AddRefs(stmt))));

    bool hasResult;
    MOZ_ASSERT(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)));

    int64_t count;
    MOZ_ASSERT(NS_SUCCEEDED(stmt->GetInt64(0, &count)));

    MOZ_ASSERT(count == 1);
  }
#endif

  // Recreate file table triggers.
  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "CREATE TRIGGER object_data_insert_trigger "
      "AFTER INSERT ON object_data "
      "WHEN NEW.file_ids IS NOT NULL "
      "BEGIN "
        "SELECT update_refcount(NULL, NEW.file_ids);"
      "END;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "CREATE TRIGGER object_data_update_trigger "
      "AFTER UPDATE OF file_ids ON object_data "
      "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
      "BEGIN "
        "SELECT update_refcount(OLD.file_ids, NEW.file_ids);"
      "END;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "CREATE TRIGGER object_data_delete_trigger "
      "AFTER DELETE ON object_data "
      "WHEN OLD.file_ids IS NOT NULL "
      "BEGIN "
        "SELECT update_refcount(OLD.file_ids, NULL);"
      "END;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Finally, turn on auto_vacuum mode. We use full auto_vacuum mode to reclaim
  // disk space on mobile devices (at the cost of some COMMIT speed), and
  // incremental auto_vacuum mode on desktop builds.
  rv = aConnection->ExecuteSimpleSQL(
#ifdef IDB_MOBILE
    NS_LITERAL_CSTRING("PRAGMA auto_vacuum = FULL;")
#else
    NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL;")
#endif
  );
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->SetSchemaVersion(MakeSchemaVersion(18, 0));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_OK;
}

nsresult
UpgradeSchemaFrom17_0To18_0(mozIStorageConnection* aConnection,
                            const nsACString& aOrigin)
{
  MOZ_ASSERT(aConnection);
  MOZ_ASSERT(!aOrigin.IsEmpty());

  PROFILER_LABEL("IndexedDB",
                 "UpgradeSchemaFrom17_0To18_0",
                 js::ProfileEntry::Category::STORAGE);

  return UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(aConnection, aOrigin);
}

nsresult
UpgradeSchemaFrom18_0To19_0(mozIStorageConnection* aConnection)
{
  AssertIsOnIOThread();
  MOZ_ASSERT(aConnection);

  nsresult rv;
  PROFILER_LABEL("IndexedDB",
                 "UpgradeSchemaFrom18_0To19_0",
                 js::ProfileEntry::Category::STORAGE);

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "ALTER TABLE object_store_index "
    "ADD COLUMN locale TEXT;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "ALTER TABLE object_store_index "
    "ADD COLUMN is_auto_locale BOOLEAN;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "ALTER TABLE index_data "
    "ADD COLUMN value_locale BLOB;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "ALTER TABLE unique_index_data "
    "ADD COLUMN value_locale BLOB;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "CREATE INDEX index_data_value_locale_index "
    "ON index_data (index_id, value_locale, object_data_key, value) "
    "WHERE value_locale IS NOT NULL;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "CREATE INDEX unique_index_data_value_locale_index "
    "ON unique_index_data (index_id, value_locale, object_data_key, value) "
    "WHERE value_locale IS NOT NULL;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->SetSchemaVersion(MakeSchemaVersion(19, 0));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  return NS_OK;
}

#if !defined(MOZ_B2G)

class NormalJSRuntime;

class UpgradeFileIdsFunction final
  : public mozIStorageFunction
{
  RefPtr<FileManager> mFileManager;
  nsAutoPtr<NormalJSRuntime> mRuntime;

public:
  UpgradeFileIdsFunction()
  {
    AssertIsOnIOThread();
  }

  nsresult
  Init(nsIFile* aFMDirectory,
       mozIStorageConnection* aConnection);

  NS_DECL_ISUPPORTS

private:
  ~UpgradeFileIdsFunction()
  {
    AssertIsOnIOThread();

    if (mFileManager) {
      mFileManager->Invalidate();
    }
  }

  NS_IMETHOD
  OnFunctionCall(mozIStorageValueArray* aArguments,
                 nsIVariant** aResult) override;
};

#endif // MOZ_B2G

nsresult
UpgradeSchemaFrom19_0To20_0(nsIFile* aFMDirectory,
                            mozIStorageConnection* aConnection)
{
  AssertIsOnIOThread();
  MOZ_ASSERT(aConnection);

  PROFILER_LABEL("IndexedDB",
                 "UpgradeSchemaFrom19_0To20_0",
                 js::ProfileEntry::Category::STORAGE);

#if defined(MOZ_B2G)

  // We don't have to do the upgrade of file ids on B2G. The old format was
  // only used by the previous single process implementation and B2G was
  // always multi process. This is a nice optimization since the upgrade needs
  // to deserialize all structured clones which reference a stored file or
  // a mutable file.
  nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

#else // MOZ_B2G

  nsCOMPtr<mozIStorageStatement> stmt;
  nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING(
    "SELECT count(*) "
    "FROM object_data "
    "WHERE file_ids IS NOT NULL"
  ), getter_AddRefs(stmt));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  int64_t count;

  {
    mozStorageStatementScoper scoper(stmt);

    bool hasResult;
    rv = stmt->ExecuteStep(&hasResult);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    if (NS_WARN_IF(!hasResult)) {
      MOZ_ASSERT(false, "This should never be possible!");
      return NS_ERROR_FAILURE;
    }

    count = stmt->AsInt64(0);
    if (NS_WARN_IF(count < 0)) {
      MOZ_ASSERT(false, "This should never be possible!");
      return NS_ERROR_FAILURE;
    }
  }

  if (count == 0) {
    // Nothing to upgrade.
    rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    return NS_OK;
  }

  RefPtr<UpgradeFileIdsFunction> function = new UpgradeFileIdsFunction();

  rv = function->Init(aFMDirectory, aConnection);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  NS_NAMED_LITERAL_CSTRING(functionName, "upgrade");

  rv = aConnection->CreateFunction(functionName, 2, function);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Disable update trigger.
  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "DROP TRIGGER object_data_update_trigger;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "UPDATE object_data "
      "SET file_ids = upgrade(file_ids, data) "
      "WHERE file_ids IS NOT NULL;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Enable update trigger.
  rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
    "CREATE TRIGGER object_data_update_trigger "
    "AFTER UPDATE OF file_ids ON object_data "
    "FOR EACH ROW "
    "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL "
    "BEGIN "
      "SELECT update_refcount(OLD.file_ids, NEW.file_ids); "
    "END;"
  ));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->RemoveFunction(functionName);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = aConnection->SetSchemaVersion(MakeSchemaVersion(20, 0));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

#endif // MOZ_B2G

  return NS_OK;
}

class UpgradeIndexDataValuesFunction final
  : public mozIStorageFunction
{
public:
  UpgradeIndexDataValuesFunction()
  {
    AssertIsOnIOThread();
  }

  NS_DECL_ISUPPORTS

private:
  ~UpgradeIndexDataValuesFunction()
  {
    AssertIsOnIOThread();
  }

  nsresult
  ReadOldCompressedIDVFromBlob(const uint8_t* aBlobData,
                               uint32_t aBlobDataLength,
                               FallibleTArray<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,
                                   FallibleTArray<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;
  }

  AutoFallibleTArray<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.get(), newIdvLength);
  
  nsCOMPtr<nsIVariant> result = new storage::AdoptedBlobVariant(data);

  newIdv.release();

  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
GetDatabaseFileURL(nsIFile* aDatabaseFile,
                   PersistenceType aPersistenceType,
                   const nsACString& aGroup,
                   const nsACString& aOrigin,
                   uint32_t aTelemetryId,
                   nsIFileURL** aResult)
{
  MOZ_ASSERT(aDatabaseFile);
  MOZ_ASSERT(aResult);

  nsCOMPtr<nsIURI> uri;
  nsresult rv = NS_NewFileURI(getter_AddRefs(uri), aDatabaseFile);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<nsIFileURL> fileUrl = do_QueryInterface(uri);
  MOZ_ASSERT(fileUrl);

  nsAutoCString type;
  PersistenceTypeToText(aPersistenceType, type);

  nsAutoCString telemetryFilenameClause;
  if (aTelemetryId) {
    telemetryFilenameClause.AssignLiteral("&telemetryFilename=indexedDB-");
    telemetryFilenameClause.AppendInt(aTelemetryId);
    telemetryFilenameClause.AppendLiteral(".sqlite");
  }

  rv = fileUrl->SetQuery(NS_LITERAL_CSTRING("persistenceType=") + type +
                         NS_LITERAL_CSTRING("&group=") + aGroup +
                         NS_LITERAL_CSTRING("&origin=") + aOrigin +
                         NS_LITERAL_CSTRING("&cache=private") +
                         telemetryFilenameClause);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  fileUrl.forget(aResult);
  return NS_OK;
}

nsresult
SetDefaultPragmas(mozIStorageConnection* aConnection)
{
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_ASSERT(aConnection);

  static const char kBuiltInPragmas[] =
    // We use foreign keys in DEBUG builds only because there is a performance
    // cost to using them.
   "PRAGMA foreign_keys = "
#ifdef DEBUG
     "ON"
#else
     "OFF"
#endif
     ";"

    // The "INSERT OR REPLACE" statement doesn't fire the update trigger,
    // instead it fires only the insert trigger. This confuses the update
    // refcount function. This behavior changes with enabled recursive triggers,
    // so the statement fires the delete trigger first and then the insert
    // trigger.
    "PRAGMA recursive_triggers = ON;"

    // We aggressively truncate the database file when idle so don't bother
    // overwriting the WAL with 0 during active periods.
    "PRAGMA secure_delete = OFF;"
  ;

  nsresult rv =
    aConnection->ExecuteSimpleSQL(
      nsDependentCString(kBuiltInPragmas,
                         LiteralStringLength(kBuiltInPragmas)));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsAutoCString pragmaStmt;
  pragmaStmt.AssignLiteral("PRAGMA synchronous = ");

  if (IndexedDatabaseManager::FullSynchronous()) {
    pragmaStmt.AppendLiteral("FULL");
  } else {
    pragmaStmt.AppendLiteral("NORMAL");
  }
  pragmaStmt.Append(';');

  rv = aConnection->ExecuteSimpleSQL(pragmaStmt);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

#ifndef IDB_MOBILE
  if (kSQLiteGrowthIncrement) {
    // This is just an optimization so ignore the failure if the disk is
    // currently too full.
    rv = aConnection->SetGrowthIncrement(kSQLiteGrowthIncrement,
                                         EmptyCString());
    if (rv != NS_ERROR_FILE_TOO_BIG && NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }
#endif // IDB_MOBILE

  return NS_OK;
}

nsresult
SetJournalMode(mozIStorageConnection* aConnection)
{
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_ASSERT(aConnection);

  // Try enabling WAL mode. This can fail in various circumstances so we have to
  // check the results here.
  NS_NAMED_LITERAL_CSTRING(journalModeQueryStart, "PRAGMA journal_mode = ");
  NS_NAMED_LITERAL_CSTRING(journalModeWAL, "wal");

  nsCOMPtr<mozIStorageStatement> stmt;
  nsresult rv =
    aConnection->CreateStatement(journalModeQueryStart + journalModeWAL,
                                 getter_AddRefs(stmt));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  bool hasResult;
  rv = stmt->ExecuteStep(&hasResult);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  MOZ_ASSERT(hasResult);

  nsCString journalMode;
  rv = stmt->GetUTF8String(0, journalMode);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  if (journalMode.Equals(journalModeWAL)) {
    // WAL mode successfully enabled. Maybe set limits on its size here.
    if (kMaxWALPages >= 0) {
      nsAutoCString pageCount;
      pageCount.AppendInt(kMaxWALPages);

      rv = aConnection->ExecuteSimpleSQL(
        NS_LITERAL_CSTRING("PRAGMA wal_autocheckpoint = ") + pageCount);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  } else {
    NS_WARNING("Failed to set WAL mode, falling back to normal journal mode.");
#ifdef IDB_MOBILE
    rv = aConnection->ExecuteSimpleSQL(journalModeQueryStart +
                                       NS_LITERAL_CSTRING("truncate"));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
#endif
  }

  return NS_OK;
}

template <class FileOrURLType>
struct StorageOpenTraits;

template <>
struct StorageOpenTraits<nsIFileURL*>
{
  static nsresult
  Open(mozIStorageService* aStorageService,
       nsIFileURL* aFileURL,
       mozIStorageConnection** aConnection)
  {
    return aStorageService->OpenDatabaseWithFileURL(aFileURL, aConnection);
  }

#ifdef DEBUG
  static void
  GetPath(nsIFileURL* aFileURL, nsCString& aPath)
  {
    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(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_TRUE(NS_SUCCEEDED(aFile->GetPath(path)));

    aPath.AssignWithConversion(path);
  }
#endif
};

template <template <class> class SmartPtr, class FileOrURLType>
struct StorageOpenTraits<SmartPtr<FileOrURLType>>
  : public StorageOpenTraits<FileOrURLType*>
{ };

template <class FileOrURLType>
nsresult
OpenDatabaseAndHandleBusy(mozIStorageService* aStorageService,
                          FileOrURLType aFileOrURL,
                          mozIStorageConnection** aConnection)
{
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_ASSERT(!IsOnBackgroundThread());
  MOZ_ASSERT(aStorageService);
  MOZ_ASSERT(aFileOrURL);
  MOZ_ASSERT(aConnection);

  nsCOMPtr<mozIStorageConnection> connection;
  nsresult rv =
    StorageOpenTraits<FileOrURLType>::Open(aStorageService,
                                           aFileOrURL,
                                           getter_AddRefs(connection));

  if (rv == NS_ERROR_STORAGE_BUSY) {
#ifdef DEBUG
    {
      nsCString path;
      StorageOpenTraits<FileOrURLType>::GetPath(aFileOrURL, path);

      nsPrintfCString message("Received NS_ERROR_STORAGE_BUSY when attempting "
                              "to open database '%s', retrying for up to 10 "
                              "seconds",
                              path.get());
      NS_WARNING(message.get());
    }
#endif

    // Another thread must be checkpointing the WAL. Wait up to 10 seconds for
    // that to complete.
    TimeStamp start = TimeStamp::NowLoRes();

    while (true) {
      PR_Sleep(PR_MillisecondsToInterval(100));

      rv = StorageOpenTraits<FileOrURLType>::Open(aStorageService,
                                                  aFileOrURL,
                                                  getter_AddRefs(connection));
      if (rv != NS_ERROR_STORAGE_BUSY ||
          TimeStamp::NowLoRes() - start > TimeDuration::FromSeconds(10)) {
        break;
      }
    }
  }

  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  connection.forget(aConnection);
  return NS_OK;
}

nsresult
CreateStorageConnection(nsIFile* aDBFile,
                        nsIFile* aFMDirectory,
                        const nsAString& aName,
                        PersistenceType aPersistenceType,
                        const nsACString& aGroup,
                        const nsACString& aOrigin,
                        uint32_t aTelemetryId,
                        mozIStorageConnection** aConnection)
{
  AssertIsOnIOThread();
  MOZ_ASSERT(aDBFile);
  MOZ_ASSERT(aFMDirectory);
  MOZ_ASSERT(aConnection);

  PROFILER_LABEL("IndexedDB",
                 "CreateStorageConnection",
                 js::ProfileEntry::Category::STORAGE);

  nsresult rv;
  bool exists;

  if (IndexedDatabaseManager::InLowDiskSpaceMode()) {
    rv = aDBFile->Exists(&exists);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    if (!exists) {
      NS_WARNING("Refusing to create database because disk space is low!");
      return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
    }
  }

  nsCOMPtr<nsIFileURL> dbFileUrl;
  rv = GetDatabaseFileURL(aDBFile,
                          aPersistenceType,
                          aGroup,
                          aOrigin,
                          aTelemetryId,
                          getter_AddRefs(dbFileUrl));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<mozIStorageService> ss =
    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<mozIStorageConnection> connection;
  rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
  if (rv == NS_ERROR_FILE_CORRUPTED) {
    // If we're just opening the database during origin initialization, then
    // we don't want to erase any files. The failure here will fail origin
    // initialization too.
    if (aName.IsVoid()) {
      return rv;
    }

    // Nuke the database file.
    rv = aDBFile->Remove(false);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    rv = aFMDirectory->Exists(&exists);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }

    if (exists) {
      bool isDirectory;
      rv = aFMDirectory->IsDirectory(&isDirectory);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
      if (NS_WARN_IF(!isDirectory)) {
        IDB_REPORT_INTERNAL_ERR();
        return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
      }

      rv = aFMDirectory->Remove(true);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }

    rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
  }

  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = SetDefaultPragmas(connection);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = connection->EnableModule(NS_LITERAL_CSTRING("filesystem"));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Check to make sure that the database schema is correct.
  int32_t schemaVersion;
  rv = connection->GetSchemaVersion(&schemaVersion);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  // Unknown schema will fail origin initialization too.
  if (!schemaVersion && aName.IsVoid()) {
    IDB_WARNING("Unable to open IndexedDB database, schema is not set!");
    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
  }

  if (schemaVersion > kSQLiteSchemaVersion) {
    IDB_WARNING("Unable to open IndexedDB database, schema is too high!");
    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
  }

  bool journalModeSet = false;

  if (schemaVersion != kSQLiteSchemaVersion) {
    const bool newDatabase = !schemaVersion;

    if (newDatabase) {
      // Set the page size first.
      if (kSQLitePageSizeOverride) {
        rv = connection->ExecuteSimpleSQL(
          nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride)
        );
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }

      // We have to set the auto_vacuum mode before opening a transaction.
      rv = connection->ExecuteSimpleSQL(
#ifdef IDB_MOBILE
        // Turn on full auto_vacuum mode to reclaim disk space on mobile
        // devices (at the cost of some COMMIT speed).
        NS_LITERAL_CSTRING("PRAGMA auto_vacuum = FULL;")
#else
        // Turn on incremental auto_vacuum mode on desktop builds.
        NS_LITERAL_CSTRING("PRAGMA auto_vacuum = INCREMENTAL;")
#endif
      );
      if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) {
        // mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE,
        // which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR.
        rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR;
      }
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      rv = SetJournalMode(connection);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      journalModeSet = true;
    } else {
#ifdef DEBUG
    // Disable foreign key support while upgrading. This has to be done before
    // starting a transaction.
    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
      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((22 << 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 {
          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_TRUE(NS_SUCCEEDED(
        connection->CreateStatement(
          NS_LITERAL_CSTRING("PRAGMA foreign_key_check;"),
          getter_AddRefs(checkStmt))));

      bool hasResult;
      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(checkStmt->ExecuteStep(&hasResult)));
      MOZ_ASSERT(!hasResult, "Database has inconsisistent foreign keys!");

      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
        connection->ExecuteSimpleSQL(
          NS_LITERAL_CSTRING("PRAGMA foreign_keys = OFF;"))));
    }
#endif

    if (kSQLitePageSizeOverride && !newDatabase) {
      nsCOMPtr<mozIStorageStatement> stmt;
      rv = connection->CreateStatement(NS_LITERAL_CSTRING(
        "PRAGMA page_size;"
      ), getter_AddRefs(stmt));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      bool hasResult;
      rv = stmt->ExecuteStep(&hasResult);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      MOZ_ASSERT(hasResult);

      int32_t pageSize;
      rv = stmt->GetInt32(0, &pageSize);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      MOZ_ASSERT(pageSize >= 512 && pageSize <= 65536);

      if (kSQLitePageSizeOverride != uint32_t(pageSize)) {
        // We must not be in WAL journal mode to change the page size.
        rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
          "PRAGMA journal_mode = DELETE;"
        ));
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }

        rv = connection->CreateStatement(NS_LITERAL_CSTRING(
          "PRAGMA journal_mode;"
        ), getter_AddRefs(stmt));
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }

        rv = stmt->ExecuteStep(&hasResult);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }

        MOZ_ASSERT(hasResult);

        nsCString journalMode;
        rv = stmt->GetUTF8String(0, journalMode);
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }

        if (journalMode.EqualsLiteral("delete")) {
          // Successfully set to rollback journal mode so changing the page size
          // is possible with a VACUUM.
          rv = connection->ExecuteSimpleSQL(
            nsPrintfCString("PRAGMA page_size = %lu;", kSQLitePageSizeOverride)
          );
          if (NS_WARN_IF(NS_FAILED(rv))) {
            return rv;
          }

          // We will need to VACUUM in order to change the page size.
          vacuumNeeded = true;
        } else {
          NS_WARNING("Failed to set journal_mode for database, unable to "
                     "change the page size!");
        }
      }
    }

    if (vacuumNeeded) {
      rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;"));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }

    if (newDatabase || vacuumNeeded) {
      if (journalModeSet) {
        // Make sure we checkpoint to get an accurate file size.
        rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
          "PRAGMA wal_checkpoint(FULL);"
        ));
        if (NS_WARN_IF(NS_FAILED(rv))) {
          return rv;
        }
      }

      int64_t fileSize;
      rv = aDBFile->GetFileSize(&fileSize);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      MOZ_ASSERT(fileSize > 0);

      PRTime vacuumTime = PR_Now();
      MOZ_ASSERT(vacuumTime);

      nsCOMPtr<mozIStorageStatement> vacuumTimeStmt;
      rv = connection->CreateStatement(NS_LITERAL_CSTRING(
        "UPDATE database "
          "SET last_vacuum_time = :time"
            ", last_vacuum_size = :size;"
      ), getter_AddRefs(vacuumTimeStmt));
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("time"),
                                           vacuumTime);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      rv = vacuumTimeStmt->BindInt64ByName(NS_LITERAL_CSTRING("size"),
                                           fileSize);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }

      rv = vacuumTimeStmt->Execute();
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
    }
  }

  if (!journalModeSet) {
    rv = SetJournalMode(connection);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return rv;
    }
  }

  connection.forget(aConnection);
  return NS_OK;
}

already_AddRefed<nsIFile>
GetFileForPath(const nsAString& aPath)
{
  MOZ_ASSERT(!aPath.IsEmpty());

  nsCOMPtr<nsIFile> file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
  if (NS_WARN_IF(!file)) {
    return nullptr;
  }

  if (NS_WARN_IF(NS_FAILED(file->InitWithPath(aPath)))) {
    return nullptr;
  }

  return file.forget();
}

nsresult
GetStorageConnection(nsIFile* aDatabaseFile,
                     PersistenceType aPersistenceType,
                     const nsACString& aGroup,
                     const nsACString& aOrigin,
                     uint32_t aTelemetryId,
                     mozIStorageConnection** aConnection)
{
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_ASSERT(!IsOnBackgroundThread());
  MOZ_ASSERT(aDatabaseFile);
  MOZ_ASSERT(aConnection);

  PROFILER_LABEL("IndexedDB",
                 "GetStorageConnection",
                 js::ProfileEntry::Category::STORAGE);

  bool exists;
  nsresult rv = aDatabaseFile->Exists(&exists);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  if (NS_WARN_IF(!exists)) {
    IDB_REPORT_INTERNAL_ERR();
    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
  }

  nsCOMPtr<nsIFileURL> dbFileUrl;
  rv = GetDatabaseFileURL(aDatabaseFile,
                          aPersistenceType,
                          aGroup,
                          aOrigin,
                          aTelemetryId,
                          getter_AddRefs(dbFileUrl));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<mozIStorageService> ss =
    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  nsCOMPtr<mozIStorageConnection> connection;
  rv = OpenDatabaseAndHandleBusy(ss, dbFileUrl, getter_AddRefs(connection));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = SetDefaultPragmas(connection);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = SetJournalMode(connection);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  connection.forget(aConnection);
  return NS_OK;
}

nsresult
GetStorageConnection(const nsAString& aDatabaseFilePath,
                     PersistenceType aPersistenceType,
                     const nsACString& aGroup,
                     const nsACString& aOrigin,
                     uint32_t aTelemetryId,
                     mozIStorageConnection** aConnection)
{
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_ASSERT(!IsOnBackgroundThread());
  MOZ_ASSERT(!aDatabaseFilePath.IsEmpty());
  MOZ_ASSERT(StringEndsWith(aDatabaseFilePath, NS_LITERAL_STRING(".sqlite")));
  MOZ_ASSERT(aConnection);

  nsCOMPtr<nsIFile> dbFile = GetFileForPath(aDatabaseFilePath);
  if (NS_WARN_IF(!dbFile)) {
    IDB_REPORT_INTERNAL_ERR();
    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
  }

  return GetStorageConnection(dbFile,
                              aPersistenceType,
                              aGroup,
                              aOrigin,
                              aTelemetryId,
                              aConnection);
}

/*******************************************************************************
 * ConnectionPool declarations
 ******************************************************************************/

class DatabaseConnection final
{
  friend class ConnectionPool;

  enum class CheckpointMode
  {
    Full,
    Restart,
    Truncate
  };

public:
  class AutoSavepoint;
  class CachedStatement;
  class UpdateRefcountFunction;

private:
  nsCOMPtr<mozIStorageConnection> mStorageConnection;
  RefPtr<FileManager> mFileManager;
  nsInterfaceHashtable<nsCStringHashKey, mozIStorageStatement>
    mCachedStatements;
  RefPtr<UpdateRefcountFunction> mUpdateRefcountFunction;
  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();

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);
};

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;
  struct 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 nsRunnable
{
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 nsRunnable
{
  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);

  ~IdleResource();
};

struct ConnectionPool::IdleDatabaseInfo final
  : public IdleResource
{
  DatabaseInfo* mDatabaseInfo;

public:
  MOZ_IMPLICIT
  IdleDatabaseInfo(DatabaseInfo* aDatabaseInfo);

  explicit
  IdleDatabaseInfo(const IdleDatabaseInfo& aOther);

  ~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);

  ~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 nsRunnable
{
  // 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
};

struct ConnectionPool::TransactionInfo final
{
  friend class nsAutoPtr<TransactionInfo>;

  DatabaseInfo* mDatabaseInfo;
  const nsID mBackgroundChildLoggingId;
  const nsCString mDatabaseId;
  const uint64_t mTransactionId;
  const int64_t mLoggingSerialNumber;
  const nsTArray<nsString> mObjectStoreNames;
  nsTHashtable<nsPtrHashKey<TransactionInfo>> mBlockedOn;
  nsTHashtable<nsPtrHashKey<TransactionInfo>> mBlocking;
  nsTArray<nsCOMPtr<nsIRunnable>> mQueuedRunnables;
  const bool mIsWriteTransaction;
  bool mRunning;

  DebugOnly<bool> mFinished;

  TransactionInfo(DatabaseInfo* aDatabaseInfo,
                  const nsID& aBackgroundChildLoggingId,
                  const nsACString& aDatabaseId,
                  uint64_t aTransactionId,
                  int64_t aLoggingSerialNumber,
                  const nsTArray<nsString>& aObjectStoreNames,
                  bool aIsWriteTransaction,
                  TransactionDatabaseOperationBase* aTransactionOp);

  void
  Schedule();

private:
  ~TransactionInfo();
};

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 nsRunnable
  , 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(&current)) && 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,
                              FallibleTArray<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,
                                     const nsAString& aFileIds,
                                     FileManager* aFileManager,
                                     StructuredCloneReadInfo* aInfo);

  // Not to be overridden by subclasses.
  NS_DECL_MOZISTORAGEPROGRESSHANDLER
};

class MOZ_STACK_CLASS DatabaseOperationBase::AutoSetProgressHandler final
{
  mozIStorageConnection* mConnection;
  DebugOnly<DatabaseOperationBase*> mDEBUGDatabaseOp;

public:
  AutoSetProgressHandler();

  ~AutoSetProgressHandler();

  nsresult
  Register(mozIStorageConnection* aConnection,
           DatabaseOperationBase* aDatabaseOp);
};

class TransactionDatabaseOperationBase
  : public DatabaseOperationBase
{
  RefPtr<TransactionBase> mTransaction;
  const int64_t mTransactionLoggingSerialNumber;
  const bool mTransactionIsAborted;

public:
  void
  AssertIsOnConnectionThread() const
#ifdef DEBUG
  ;
#else
  { }
#endif

  void
  DispatchToConnectionPool();

  TransactionBase*
  Transaction() const
  {
    MOZ_ASSERT(mTransaction);

    return mTransaction;
  }

  // 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;

  // 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
  RunOnOwningThread();

  // Not to be overridden by subclasses.
  NS_DECL_NSIRUNNABLE
};

class Factory final
  : public PBackgroundIDBFactoryParent
{
  // Counts the number of "live" Factory instances that have not yet had
  // ActorDestroy called.
  static uint64_t sFactoryInstanceCount;

  RefPtr<DatabaseLoggingInfo> mLoggingInfo;

  DebugOnly<bool> mActorDestroyed;

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 nsRunnable
{
  nsCOMPtr<nsIEventTarget> mOwningThread;
  const nsCString mDatabaseId;
  nsCOMPtr<nsIRunnable> mCallback;

  enum class State
  {
    Initial = 0,
    WaitingForTransactions,
    WaitingForFileHandles,
    Complete
  } mState;

public:
  WaitForTransactionsHelper(const nsCString& aDatabaseId,
                            nsIRunnable* aCallback)
    : mOwningThread(NS_GetCurrentThread())
    , 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 OptionalContentId mOptionalContentParentId;
  const nsCString mGroup;
  const nsCString mOrigin;
  const nsCString mId;
  const nsString mFilePath;
  uint32_t mActiveMutableFileCount;
  const uint32_t mTelemetryId;
  const PersistenceType mPersistenceType;
  const bool mFileHandleDisabled;
  const bool mChromeWriteAccessAllowed;
  bool mClosed;
  bool mInvalidated;
  bool mActorWasAlive;
  bool mActorDestroyed;
  bool mMetadataCleanedUp;

public:
  // Created by OpenDatabaseOp.
  Database(Factory* aFactory,
           const PrincipalInfo& aPrincipalInfo,
           const OptionalContentId& aOptionalContentParentId,
           const nsACString& aGroup,
           const nsACString& aOrigin,
           uint32_t aTelemetryId,
           FullDatabaseMetadata* aMetadata,
           FileManager* aFileManager,
           already_AddRefed<DirectoryLock> aDirectoryLock,
           bool aFileHandleDisabled,
           bool aChromeWriteAccessAllowed);

  void
  AssertIsOnConnectionThread() const
  {
#ifdef DEBUG
    if (mConnection) {
      MOZ_ASSERT(mConnection);
      mConnection->AssertIsOnConnectionThread();
    } else {
      MOZ_ASSERT(!NS_IsMainThread());
      MOZ_ASSERT(!IsOnBackgroundThread());
      MOZ_ASSERT(mInvalidated);
    }
#endif
  }

  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::Database)

  void
  Invalidate();

  const PrincipalInfo&
  GetPrincipalInfo() const
  {
    return mPrincipalInfo;
  }

  bool
  IsOwnedByProcess(ContentParentId aContentParentId) const
  {
    MOZ_ASSERT(mOptionalContentParentId.type() != OptionalContentId::T__None);

    return
      mOptionalContentParentId.type() == OptionalContentId::TContentParentId &&
      mOptionalContentParentId.get_ContentParentId() == aContentParentId;
  }

  const nsCString&
  Group() const
  {
    return mGroup;
  }

  const nsCString&
  Origin() const
  {
    return mOrigin;
  }

  const nsCString&
  Id() const
  {
    return mId;
  }

  uint32_t
  TelemetryId() const
  {
    return mTelemetryId;
  }

  PersistenceType
  Type() const
  {
    return mPersistenceType;
  }

  const nsString&
  FilePath() const
  {
    return mFilePath;
  }

  FileManager*
  GetFileManager() const
  {
    return mFileManager;
  }

  FullDatabaseMetadata*
  Metadata() const
  {
    MOZ_ASSERT(mMetadata);
    return mMetadata;
  }

  PBackgroundParent*
  GetBackgroundParent() const
  {
    AssertIsOnBackgroundThread();
    MOZ_ASSERT(!IsActorDestroyed());

    return Manager()->Manager();
  }

  DatabaseLoggingInfo*
  GetLoggingInfo() const
  {
    AssertIsOnBackgroundThread();
    MOZ_ASSERT(mFactory);

    return mFactory->GetLoggingInfo();
  }

  void
  ReleaseTransactionThreadObjects();

  void
  ReleaseBackgroundThreadObjects();

  bool
  RegisterTransaction(TransactionBase* aTransaction);

  void
  UnregisterTransaction(TransactionBase* aTransaction);

  bool
  IsFileHandleDisabled() const
  {
    return mFileHandleDisabled;
  }

  bool
  RegisterMutableFile(MutableFile* aMutableFile);

  void
  UnregisterMutableFile(MutableFile* aMutableFile);

  void
  NoteActiveMutableFile();

  void
  NoteInactiveMutableFile();

  void
  SetActorAlive();

  bool
  IsActorAlive() const
  {
    AssertIsOnBackgroundThread();

    return mActorWasAlive && !mActorDestroyed;
  }

  bool
  IsActorDestroyed() const
  {
    AssertIsOnBackgroundThread();

    return mActorWasAlive && mActorDestroyed;
  }

  bool
  IsClosed() const
  {
    AssertIsOnBackgroundThread();

    return mClosed;
  }

  bool
  IsInvalidated() const
  {
    AssertIsOnBackgroundThread();

    return mInvalidated;
  }

  nsresult
  EnsureConnection();

  DatabaseConnection*
  GetConnection() const
  {
#ifdef DEBUG
    if (mConnection) {
      mConnection->AssertIsOnConnectionThread();
    }
#endif

    return mConnection;
  }

private:
  // Reference counted.
  ~Database()
  {
    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;
};

class DatabaseFile final
  : public PBackgroundIDBDatabaseFileParent
{
  friend class Database;

  RefPtr<BlobImpl> mBlobImpl;
  RefPtr<FileInfo> mFileInfo;

public:
  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::indexedDB::DatabaseFile);

  FileInfo*
  GetFileInfo() const
  {
    AssertIsOnBackgroundThread();

    return mFileInfo;
  }

  already_AddRefed<nsIInputStream>
  GetInputStream() const
  {
    AssertIsOnBackgroundThread();

    nsCOMPtr<nsIInputStream> inputStream;
    if (mBlobImpl) {
      ErrorResult rv;
      mBlobImpl->GetInternalStream(getter_AddRefs(inputStream), rv);
      MOZ_ALWAYS_TRUE(!rv.Failed());
    }

    return inputStream.forget();
  }

  void
  ClearInputStream()
  {
    AssertIsOnBackgroundThread();

    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();

    mBlobImpl = nullptr;
    mFileInfo = nullptr;
  }
};

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
  RecvCreateIndex(const int64_t& aObjectStoreId,
                  const IndexMetadata& aMetadata) override;

  virtual bool
  RecvDeleteIndex(const int64_t& aObjectStoreId,
                  const int64_t& aIndexId) 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 mGroup;
  nsCString mOrigin;
  nsCString mDatabaseId;
  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

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;

  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);

  void
  FinishOpen();

  nsresult
  QuotaManagerOpen();

  void
  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;

  OptionalContentId mOptionalContentParentId;

  RefPtr<FullDatabaseMetadata> mMetadata;

  uint64_t mRequestedVersion;
  nsString mDatabaseFilePath;
  RefPtr<FileManager> mFileManager;

  RefPtr<Database> mDatabase;
  RefPtr<VersionChangeTransaction> mVersionChangeTransaction;

  // This is only set while a VersionChangeOp is live. It holds a strong
  // reference to its OpenDatabaseOp object so this is a weak pointer to avoid
  // cycles.
  VersionChangeOp* mVersionChangeOp;

  uint32_t mTelemetryId;

public:
  OpenDatabaseOp(Factory* aFactory,
                 already_AddRefed<ContentParent> aContentParent,
                 const CommonFactoryRequestParams& aParams);

  bool
  IsOtherProcessActor() const
  {
    MOZ_ASSERT(mOptionalContentParentId.type() != OptionalContentId::T__None);

    return mOptionalContentParentId.type() ==
             OptionalContentId::TContentParentId;
  }

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;

#ifdef ENABLE_INTL_API
  static nsresult
  UpdateLocaleAwareIndex(mozIStorageConnection* aConnection,
                         const IndexMetadata& aIndexMetadata,
                         const nsCString& aLocale);
#endif
};

class OpenDatabaseOp::VersionChangeOp final
  : public TransactionDatabaseOperationBase
{
  friend class OpenDatabaseOp;

  RefPtr<OpenDatabaseOp> mOpenDatabaseOp;
  const uint64_t mRequestedVersion;
  uint64_t mPreviousVersion;

private:
  explicit
  VersionChangeOp(OpenDatabaseOp* aOpenDatabaseOp)
    : TransactionDatabaseOperationBase(
                                     aOpenDatabaseOp->mVersionChangeTransaction,
                                     aOpenDatabaseOp->LoggingSerialNumber())
    , mOpenDatabaseOp(aOpenDatabaseOp)
    , mRequestedVersion(aOpenDatabaseOp->mRequestedVersion)
    , mPreviousVersion(aOpenDatabaseOp->mMetadata->mCommonMetadata.version())
  {
    MOZ_ASSERT(aOpenDatabaseOp);
    MOZ_ASSERT(mRequestedVersion);
  }

  ~VersionChangeOp()
  {
    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(