Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/cache/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 123 kB image not shown  

Quelle  DBSchema.cpp   Sprache: C

 
/* -*- 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 "mozilla/dom/cache/DBSchema.h"

#include "ipc/IPCMessageUtils.h"
#include "mozIStorageConnection.h"
#include "mozIStorageFunction.h"
#include "mozIStorageStatement.h"
#include "mozStorageHelper.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/StaticPrefs_extensions.h"
#include "mozilla/dom/HeadersBinding.h"
#include "mozilla/dom/InternalHeaders.h"
#include "mozilla/dom/InternalResponse.h"
#include "mozilla/dom/RequestBinding.h"
#include "mozilla/dom/ResponseBinding.h"
#include "mozilla/dom/cache/CacheCommon.h"
#include "mozilla/dom/cache/CacheTypes.h"
#include "mozilla/dom/cache/FileUtils.h"
#include "mozilla/dom/cache/SavedTypes.h"
#include "mozilla/dom/cache/TypeUtils.h"
#include "mozilla/dom/cache/Types.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "mozilla/psm/TransportSecurityInfo.h"
#include "mozilla/storage/Variant.h"
#include "nsCOMPtr.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsComponentManagerUtils.h"
#include "nsHttp.h"
#include "nsIContentPolicy.h"
#include "nsICryptoHash.h"
#include "nsIURI.h"
#include "nsNetCID.h"
#include "nsPrintfCString.h"
#include "nsTArray.h"

namespace mozilla::dom::cache::db {
const int32_t kFirstShippedSchemaVersion = 15;
namespace {
// ## Firefox 57 Cache API v25/v26/v27 Schema Hack Info
// ### Overview
// In Firefox 57 we introduced Cache API schema version 26 and Quota Manager
// schema v3 to support tracking padding for opaque responses.  Unfortunately,
// Firefox 57 is a big release that may potentially result in users downgrading
// to Firefox 56 due to 57 retiring add-ons.  These schema changes have the
// unfortunate side-effect of causing QuotaManager and all its clients to break
// if the user downgrades to 56.  In order to avoid making a bad situation
// worse, we're now retrofitting 57 so that Firefox 56 won't freak out.
//
// ### Implementation
// We're introducing a new schema version 27 that uses an on-disk schema version
// of v25.  We differentiate v25 from v27 by the presence of the column added
// by v26.  This translates to:
// - v25: on-disk schema=25, no "response_padding_size" column in table
//   "entries".
// - v26: on-disk schema=26, yes "response_padding_size" column in table
//   "entries".
// - v27: on-disk schema=25, yes "response_padding_size" column in table
//   "entries".
//
// ### Fallout
// Firefox 57 is happy because it sees schema 27 and everything is as it
// expects.
//
// Firefox 56 non-DEBUG build is fine/happy, but DEBUG builds will not be.
// - Our QuotaClient will invoke `NS_WARNING("Unknown Cache file found!");`
//   at QuotaManager init time.  This is harmless but annoying and potentially
//   misleading.
// - The DEBUG-only Validate() call will error out whenever an attempt is made
//   to open a DOM Cache database because it will notice the schema is broken
//   and there is no attempt at recovery.
//
const int32_t kHackyDowngradeSchemaVersion = 25;
const int32_t kHackyPaddingSizePresentVersion = 27;
//
// Update this whenever the DB schema is changed.
const int32_t kLatestSchemaVersion = 29;
// ---------
// The following constants define the SQL schema.  These are defined in the
// same order the SQL should be executed in CreateOrMigrateSchema().  They are
// broken out as constants for convenient use in validation and migration.
// ---------
// The caches table is the single source of truth about what Cache
// objects exist for the origin.  The contents of the Cache are stored
// in the entries table that references back to caches.
//
// The caches table is also referenced from storage.  Rows in storage
// represent named Cache objects.  There are cases, however, where
// a Cache can still exist, but not be in a named Storage.  For example,
// when content is still using the Cache after CacheStorage::Delete()
// has been run.
//
// For now, the caches table mainly exists for data integrity with
// foreign keys, but could be expanded to contain additional cache object
// information.
//
// AUTOINCREMENT is necessary to prevent CacheId values from being reused.
const char kTableCaches[] =
    "CREATE TABLE caches ("
    "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT "
    ")";

// Security blobs are quite large and duplicated for every Response from
// the same https origin.  This table is used to de-duplicate this data.
const char kTableSecurityInfo[] =
    "CREATE TABLE security_info ("
    "id INTEGER NOT NULL PRIMARY KEY, "
    "hash BLOB NOT NULL, "  // first 8-bytes of the sha1 hash of data column
    "data BLOB NOT NULL, "  // full security info data, usually a few KB
    "refcount INTEGER NOT NULL"
    ")";

// Index the smaller hash value instead of the large security data blob.
const char kIndexSecurityInfoHash[] =
    "CREATE INDEX security_info_hash_index ON security_info (hash)";

const char kTableEntries[] =
    "CREATE TABLE entries ("
    "id INTEGER NOT NULL PRIMARY KEY, "
    "request_method TEXT NOT NULL, "
    "request_url_no_query TEXT NOT NULL, "
    "request_url_no_query_hash BLOB NOT NULL, "  // first 8-bytes of sha1 hash
    "request_url_query TEXT NOT NULL, "
    "request_url_query_hash BLOB NOT NULL, "  // first 8-bytes of sha1 hash
    "request_referrer TEXT NOT NULL, "
    "request_headers_guard INTEGER NOT NULL, "
    "request_mode INTEGER NOT NULL, "
    "request_credentials INTEGER NOT NULL, "
    "request_contentpolicytype INTEGER NOT NULL, "
    "request_cache INTEGER NOT NULL, "
    "request_body_id TEXT NULL, "
    "response_type INTEGER NOT NULL, "
    "response_status INTEGER NOT NULL, "
    "response_status_text TEXT NOT NULL, "
    "response_headers_guard INTEGER NOT NULL, "
    "response_body_id TEXT NULL, "
    "response_security_info_id INTEGER NULL REFERENCES security_info(id), "
    "response_principal_info TEXT NOT NULL, "
    "cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, "
    "request_redirect INTEGER NOT NULL, "
    "request_referrer_policy INTEGER NOT NULL, "
    "request_integrity TEXT NOT NULL, "
    "request_url_fragment TEXT NOT NULL, "
    "response_padding_size INTEGER NULL, "
    "request_body_disk_size INTEGER NULL, "
    "response_body_disk_size INTEGER NULL "
    // New columns must be added at the end of table to migrate and
    // validate properly.
    ")";
// Create an index to support the QueryCache() matching algorithm.  This
// needs to quickly find entries in a given Cache that match the request
// URL.  The url query is separated in order to support the ignoreSearch
// option.  Finally, we index hashes of the URL values instead of the
// actual strings to avoid excessive disk bloat.  The index will duplicate
// the contents of the columsn in the index.  The hash index will prune
// the vast majority of values from the query result so that normal
// scanning only has to be done on a few values to find an exact URL match.
const char kIndexEntriesRequest[] =
    "CREATE INDEX entries_request_match_index "
    "ON entries (cache_id, request_url_no_query_hash, "
    "request_url_query_hash)";

const char kTableRequestHeaders[] =
    "CREATE TABLE request_headers ("
    "name TEXT NOT NULL, "
    "value TEXT NOT NULL, "
    "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
    ")";

const char kTableResponseHeaders[] =
    "CREATE TABLE response_headers ("
    "name TEXT NOT NULL, "
    "value TEXT NOT NULL, "
    "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
    ")";

// We need an index on response_headers, but not on request_headers,
// because we quickly need to determine if a VARY header is present.
const char kIndexResponseHeadersName[] =
    "CREATE INDEX response_headers_name_index "
    "ON response_headers (name)";

const char kTableResponseUrlList[] =
    "CREATE TABLE response_url_list ("
    "url TEXT NOT NULL, "
    "entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
    ")";

// NOTE: key allows NULL below since that is how "" is represented
//       in a BLOB column.  We use BLOB to avoid encoding issues
//       with storing DOMStrings.
const char kTableStorage[] =
    "CREATE TABLE storage ("
    "namespace INTEGER NOT NULL, "
    "key BLOB NULL, "
    "cache_id INTEGER NOT NULL REFERENCES caches(id), "
    "PRIMARY KEY(namespace, key) "
    ")";

const char kTableUsageInfo[] =
    "CREATE TABLE usage_info ("
    "id INTEGER NOT NULL PRIMARY KEY, "
    "total_disk_usage INTEGER NOT NULL "
    ")";

const char kTriggerEntriesInsert[] =
    "CREATE TRIGGER entries_insert_trigger "
    "AFTER INSERT ON entries "
    "FOR EACH ROW "
    "BEGIN "
    "UPDATE usage_info SET total_disk_usage = total_disk_usage + "
    "ifnull(NEW.request_body_disk_size, 0) + "
    "ifnull(NEW.response_body_disk_size, 0) "
    "WHERE usage_info.id = 1; "
    "END";

const char kTriggerEntriesUpdate[] =
    "CREATE TRIGGER entries_update_trigger "
    "AFTER UPDATE ON entries "
    "FOR EACH ROW "
    "BEGIN "
    "UPDATE usage_info SET total_disk_usage = total_disk_usage - "
    "ifnull(OLD.request_body_disk_size, 0) + "
    "ifnull(NEW.request_body_disk_size, 0) - "
    "ifnull(OLD.response_body_disk_size, 0) + "
    "ifnull(NEW.response_body_disk_size, 0) "
    "WHERE usage_info.id = 1; "
    "END";

const char kTriggerEntriesDelete[] =
    "CREATE TRIGGER entries_delete_trigger "
    "AFTER DELETE ON entries "
    "FOR EACH ROW "
    "BEGIN "
    "UPDATE usage_info SET total_disk_usage = total_disk_usage - "
    "ifnull(OLD.request_body_disk_size, 0) - "
    "ifnull(OLD.response_body_disk_size, 0) "
    "WHERE usage_info.id = 1; "
    "END";

// ---------
// End schema definition
// ---------

const uint32_t kMaxEntriesPerStatement = 255;

const uint32_t kPageSize = 4 * 1024;

// Grow the database in chunks to reduce fragmentation
const uint32_t kGrowthSize = 32 * 1024;
const uint32_t kGrowthPages = kGrowthSize / kPageSize;
static_assert(kGrowthSize % kPageSize == 0,
              "Growth size must be multiple of page size");

// Only release free pages when we have more than this limit
const int32_t kMaxFreePages = kGrowthPages;

// Limit WAL journal to a reasonable size
const uint32_t kWalAutoCheckpointSize = 512 * 1024;
const uint32_t kWalAutoCheckpointPages = kWalAutoCheckpointSize / kPageSize;
static_assert(kWalAutoCheckpointSize % kPageSize == 0,
              "WAL checkpoint size must be multiple of page size");

}  // namespace

// If any of the static_asserts below fail, it means that you have changed
// the corresponding WebIDL enum in a way that may be incompatible with the
// existing data stored in the DOM Cache.  You would need to update the Cache
// database schema accordingly and adjust the failing static_assert.
static_assert(int(HeadersGuardEnum::None) == 0 &&
                  int(HeadersGuardEnum::Request) == 1 &&
                  int(HeadersGuardEnum::Request_no_cors) == 2 &&
                  int(HeadersGuardEnum::Response) == 3 &&
                  int(HeadersGuardEnum::Immutable) == 4 &&
                  ContiguousEnumSize<HeadersGuardEnum>::value == 5,
              "HeadersGuardEnum values are as expected");
static_assert(int(ReferrerPolicy::_empty) == 0 &&
                  int(ReferrerPolicy::No_referrer) == 1 &&
                  int(ReferrerPolicy::No_referrer_when_downgrade) == 2 &&
                  int(ReferrerPolicy::Origin) == 3 &&
                  int(ReferrerPolicy::Origin_when_cross_origin) == 4 &&
                  int(ReferrerPolicy::Unsafe_url) == 5 &&
                  int(ReferrerPolicy::Same_origin) == 6 &&
                  int(ReferrerPolicy::Strict_origin) == 7 &&
                  int(ReferrerPolicy::Strict_origin_when_cross_origin) == 8 &&
                  ContiguousEnumSize<ReferrerPolicy>::value == 9,
              "ReferrerPolicy values are as expected");
static_assert(int(RequestMode::Same_origin) == 0 &&
                  int(RequestMode::No_cors) == 1 &&
                  int(RequestMode::Cors) == 2 &&
                  int(RequestMode::Navigate) == 3 &&
                  ContiguousEnumSize<RequestMode>::value == 4,
              "RequestMode values are as expected");
static_assert(int(RequestCredentials::Omit) == 0 &&
                  int(RequestCredentials::Same_origin) == 1 &&
                  int(RequestCredentials::Include) == 2 &&
                  ContiguousEnumSize<RequestCredentials>::value == 3,
              "RequestCredentials values are as expected");
static_assert(int(RequestCache::Default) == 0 &&
                  int(RequestCache::No_store) == 1 &&
                  int(RequestCache::Reload) == 2 &&
                  int(RequestCache::No_cache) == 3 &&
                  int(RequestCache::Force_cache) == 4 &&
                  int(RequestCache::Only_if_cached) == 5 &&
                  ContiguousEnumSize<RequestCache>::value == 6,
              "RequestCache values are as expected");
static_assert(int(RequestRedirect::Follow) == 0 &&
                  int(RequestRedirect::Error) == 1 &&
                  int(RequestRedirect::Manual) == 2 &&
                  ContiguousEnumSize<RequestRedirect>::value == 3,
              "RequestRedirect values are as expected");
static_assert(int(ResponseType::Basic) == 0 && int(ResponseType::Cors) == 1 &&
                  int(ResponseType::Default) == 2 &&
                  int(ResponseType::Error) == 3 &&
                  int(ResponseType::Opaque) == 4 &&
                  int(ResponseType::Opaqueredirect) == 5 &&
                  ContiguousEnumSize<ResponseType>::value == 6,
              "ResponseType values are as expected");

// If the static_asserts below fails, it means that you have changed the
// Namespace enum in a way that may be incompatible with the existing data
// stored in the DOM Cache.  You would need to update the Cache database schema
// accordingly and adjust the failing static_assert.
static_assert(DEFAULT_NAMESPACE == 0 && CHROME_ONLY_NAMESPACE == 1 &&
                  NUMBER_OF_NAMESPACES == 2,
              "Namespace values are as expected");

// If the static_asserts below fails, it means that you have changed the
// nsContentPolicy enum in a way that may be incompatible with the existing data
// stored in the DOM Cache.  You would need to update the Cache database schema
// accordingly and adjust the failing static_assert.
static_assert(
    nsIContentPolicy::TYPE_INVALID == 0 && nsIContentPolicy::TYPE_OTHER == 1 &&
        nsIContentPolicy::TYPE_SCRIPT == 2 &&
        nsIContentPolicy::TYPE_IMAGE == 3 &&
        nsIContentPolicy::TYPE_STYLESHEET == 4 &&
        nsIContentPolicy::TYPE_OBJECT == 5 &&
        nsIContentPolicy::TYPE_DOCUMENT == 6 &&
        nsIContentPolicy::TYPE_SUBDOCUMENT == 7 &&
        nsIContentPolicy::TYPE_PING == 10 &&
        nsIContentPolicy::TYPE_XMLHTTPREQUEST == 11 &&
        nsIContentPolicy::TYPE_OBJECT_SUBREQUEST == 12 &&
        nsIContentPolicy::TYPE_DTD == 13 && nsIContentPolicy::TYPE_FONT == 14 &&
        nsIContentPolicy::TYPE_MEDIA == 15 &&
        nsIContentPolicy::TYPE_WEBSOCKET == 16 &&
        nsIContentPolicy::TYPE_CSP_REPORT == 17 &&
        nsIContentPolicy::TYPE_XSLT == 18 &&
        nsIContentPolicy::TYPE_BEACON == 19 &&
        nsIContentPolicy::TYPE_FETCH == 20 &&
        nsIContentPolicy::TYPE_IMAGESET == 21 &&
        nsIContentPolicy::TYPE_WEB_MANIFEST == 22 &&
        nsIContentPolicy::TYPE_INTERNAL_SCRIPT == 23 &&
        nsIContentPolicy::TYPE_INTERNAL_WORKER == 24 &&
        nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER == 25 &&
        nsIContentPolicy::TYPE_INTERNAL_EMBED == 26 &&
        nsIContentPolicy::TYPE_INTERNAL_OBJECT == 27 &&
        nsIContentPolicy::TYPE_INTERNAL_FRAME == 28 &&
        nsIContentPolicy::TYPE_INTERNAL_IFRAME == 29 &&
        nsIContentPolicy::TYPE_INTERNAL_AUDIO == 30 &&
        nsIContentPolicy::TYPE_INTERNAL_VIDEO == 31 &&
        nsIContentPolicy::TYPE_INTERNAL_TRACK == 32 &&
        nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_ASYNC == 33 &&
        nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE == 34 &&
        nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER == 35 &&
        nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD == 36 &&
        nsIContentPolicy::TYPE_INTERNAL_IMAGE == 37 &&
        nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD == 38 &&
        nsIContentPolicy::TYPE_INTERNAL_STYLESHEET == 39 &&
        nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD == 40 &&
        nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON == 41 &&
        nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS == 42 &&
        nsIContentPolicy::TYPE_SAVEAS_DOWNLOAD == 43 &&
        nsIContentPolicy::TYPE_SPECULATIVE == 44 &&
        nsIContentPolicy::TYPE_INTERNAL_MODULE == 45 &&
        nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD == 46 &&
        nsIContentPolicy::TYPE_INTERNAL_DTD == 47 &&
        nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD == 48 &&
        nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET == 49 &&
        nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET == 50 &&
        nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD == 51 &&
        nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT == 52 &&
        nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT == 53 &&
        nsIContentPolicy::TYPE_INTERNAL_FETCH_PRELOAD == 54 &&
        nsIContentPolicy::TYPE_UA_FONT == 55 &&
        nsIContentPolicy::TYPE_WEB_IDENTITY == 57 &&
        nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE == 58 &&
        nsIContentPolicy::TYPE_WEB_TRANSPORT == 59 &&
        nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST_SYNC == 60 &&
        nsIContentPolicy::TYPE_INTERNAL_EXTERNAL_RESOURCE == 61 &&
        nsIContentPolicy::TYPE_JSON == 62 &&
        nsIContentPolicy::TYPE_INTERNAL_JSON_PRELOAD == 63 &&
        nsIContentPolicy::TYPE_END == 64,
    "nsContentPolicyType values are as expected");

namespace {

using EntryId = int32_t;

struct IdCount {
  explicit IdCount(int32_t aId) : mId(aId), mCount(1) {}
  int32_t mId;
  int32_t mCount;
};

using EntryIds = AutoTArray<EntryId, 256>;

static Result<EntryIds, nsresult> QueryAll(mozIStorageConnection& aConn,
                                           CacheId aCacheId);
static Result<EntryIds, nsresult> QueryCache(mozIStorageConnection& aConn,
                                             CacheId aCacheId,
                                             const CacheRequest& aRequest,
                                             const CacheQueryParams& aParams,
                                             uint32_t aMaxResults = UINT32_MAX);
static Result<bool, nsresult> MatchByVaryHeader(mozIStorageConnection& aConn,
                                                const CacheRequest& aRequest,
                                                EntryId entryId);
// Returns a success tuple containing the deleted body ids, deleted security ids
// and deleted padding size.
static Result<std::tuple<nsTArray<nsID>, AutoTArray<IdCount, 16>, int64_t>,
              nsresult>
DeleteEntries(mozIStorageConnection& aConn,
              const nsTArray<EntryId>& aEntryIdList);

static Result<std::tuple<nsTArray<nsID>, AutoTArray<IdCount, 16>, int64_t>,
              nsresult>
DeleteAllCacheEntries(mozIStorageConnection& aConn, CacheId& aCacheId);

static Result<int32_t, nsresult> InsertSecurityInfo(
    mozIStorageConnection& aConn, nsICryptoHash& aCrypto,
    nsITransportSecurityInfo* aSecurityInfo);
static nsresult DeleteSecurityInfo(mozIStorageConnection& aConn, int32_t aId,
                                   int32_t aCount);
static nsresult DeleteSecurityInfoList(
    mozIStorageConnection& aConn,
    const nsTArray<IdCount>& aDeletedStorageIdList);
static nsresult InsertEntry(mozIStorageConnection& aConn, CacheId aCacheId,
                            const CacheRequest& aRequest,
                            const nsID* aRequestBodyId,
                            const CacheResponse& aResponse,
                            const nsID* aResponseBodyId);
static Result<SavedResponse, nsresult> ReadResponse(
    mozIStorageConnection& aConn, EntryId aEntryId);
static Result<SavedRequest, nsresult> ReadRequest(mozIStorageConnection& aConn,
                                                  EntryId aEntryId);

static void AppendListParamsToQuery(nsACString& aQuery, size_t aLen);
static nsresult BindListParamsToQuery(mozIStorageStatement& aState,
                                      const Span<const EntryId>& aEntryIdList);
static nsresult BindId(mozIStorageStatement& aState, const nsACString& aName,
                       const nsID* aId);
static Result<nsID, nsresult> ExtractId(mozIStorageStatement& aState,
                                        uint32_t aPos);
static Result<NotNull<nsCOMPtr<mozIStorageStatement>>, nsresult>
CreateAndBindKeyStatement(mozIStorageConnection& aConn,
                          const char* aQueryFormat, const nsAString& aKey);
static Result<nsAutoCString, nsresult> HashCString(nsICryptoHash& aCrypto,
                                                   const nsACString& aIn);
Result<int32_t, nsresult> GetEffectiveSchemaVersion(
    mozIStorageConnection& aConn);
nsresult Validate(mozIStorageConnection& aConn);
nsresult Migrate(nsIFile& aDBDir, mozIStorageConnection& aConn);
}  // namespace

class MOZ_RAII AutoDisableForeignKeyChecking {
 public:
  explicit AutoDisableForeignKeyChecking(mozIStorageConnection* aConn)
      : mConn(aConn), mForeignKeyCheckingDisabled(false) {
    QM_TRY_INSPECT(const auto& state,
                   quota::CreateAndExecuteSingleStepStatement(
                       *mConn, "PRAGMA foreign_keys;"_ns),
                   QM_VOID);

    QM_TRY_INSPECT(const int32_t& mode,
                   MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0), QM_VOID);

    if (mode) {
      QM_WARNONLY_TRY(MOZ_TO_RESULT(mConn->ExecuteSimpleSQL(
                                        "PRAGMA foreign_keys = OFF;"_ns))
                          .andThen([this](const auto) -> Result<Ok, nsresult> {
                            mForeignKeyCheckingDisabled = true;
                            return Ok{};
                          }));
    }
  }

  ~AutoDisableForeignKeyChecking() {
    if (mForeignKeyCheckingDisabled) {
      QM_WARNONLY_TRY(QM_TO_RESULT(
          mConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
    }
  }

 private:
  nsCOMPtr<mozIStorageConnection> mConn;
  bool mForeignKeyCheckingDisabled;
};

nsresult CreateOrMigrateSchema(nsIFile& aDBDir, mozIStorageConnection& aConn) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY_UNWRAP(int32_t schemaVersion, GetEffectiveSchemaVersion(aConn));

  if (schemaVersion == kLatestSchemaVersion) {
    // We already have the correct schema version.  Validate it matches
    // our expected schema and then proceed.
    QM_TRY(MOZ_TO_RESULT(Validate(aConn)));

    return NS_OK;
  }

  // Turn off checking foreign keys before starting a transaction, and restore
  // it once we're done.
  AutoDisableForeignKeyChecking restoreForeignKeyChecking(&aConn);
  mozStorageTransaction trans(&aConn, false,
                              mozIStorageConnection::TRANSACTION_IMMEDIATE);

  QM_TRY(MOZ_TO_RESULT(trans.Start()));

  const bool migrating = schemaVersion != 0;

  if (migrating) {
    // A schema exists, but its not the current version.  Attempt to
    // migrate it to our new schema.
    QM_TRY(MOZ_TO_RESULT(Migrate(aDBDir, aConn)));
  } else {
    // There is no schema installed.  Create the database from scratch.
    QM_TRY(
        MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsLiteralCString(kTableCaches))));
    QM_TRY(MOZ_TO_RESULT(
        aConn.ExecuteSimpleSQL(nsLiteralCString(kTableSecurityInfo))));
    QM_TRY(MOZ_TO_RESULT(
        aConn.ExecuteSimpleSQL(nsLiteralCString(kIndexSecurityInfoHash))));
    QM_TRY(
        MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsLiteralCString(kTableEntries))));
    QM_TRY(MOZ_TO_RESULT(
        aConn.ExecuteSimpleSQL(nsLiteralCString(kIndexEntriesRequest))));
    QM_TRY(MOZ_TO_RESULT(
        aConn.ExecuteSimpleSQL(nsLiteralCString(kTableRequestHeaders))));
    QM_TRY(MOZ_TO_RESULT(
        aConn.ExecuteSimpleSQL(nsLiteralCString(kTableResponseHeaders))));
    QM_TRY(MOZ_TO_RESULT(
        aConn.ExecuteSimpleSQL(nsLiteralCString(kIndexResponseHeadersName))));
    QM_TRY(MOZ_TO_RESULT(
        aConn.ExecuteSimpleSQL(nsLiteralCString(kTableResponseUrlList))));
    QM_TRY(
        MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsLiteralCString(kTableStorage))));
    QM_TRY(MOZ_TO_RESULT(
        aConn.ExecuteSimpleSQL(nsLiteralCString(kTableUsageInfo))));
    QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(
        nsLiteralCString("INSERT INTO usage_info VALUES(1, 0);"))));
    QM_TRY(MOZ_TO_RESULT(
        aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesInsert))));
    QM_TRY(MOZ_TO_RESULT(
        aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesUpdate))));
    QM_TRY(MOZ_TO_RESULT(
        aConn.ExecuteSimpleSQL(nsLiteralCString(kTriggerEntriesDelete))));
    QM_TRY(MOZ_TO_RESULT(aConn.SetSchemaVersion(kLatestSchemaVersion)));
    QM_TRY_UNWRAP(schemaVersion, GetEffectiveSchemaVersion(aConn));
  }

  QM_TRY(MOZ_TO_RESULT(Validate(aConn)));
  QM_TRY(MOZ_TO_RESULT(trans.Commit()));

  if (migrating) {
    // Migrations happen infrequently and reflect a chance in DB structure.
    // This is a good time to rebuild the database.  It also helps catch
    // if a new migration is incorrect by fast failing on the corruption.
    // Unfortunately, this must be performed outside of the transaction.

    QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("VACUUM"_ns)));
  }

  return NS_OK;
}

nsresult InitializeConnection(mozIStorageConnection& aConn) {
  MOZ_ASSERT(!NS_IsMainThread());

  // This function needs to perform per-connection initialization tasks that
  // need to happen regardless of the schema.

  // Note, the default encoding of UTF-8 is preferred.  mozStorage does all
  // the work necessary to convert UTF-16 nsString values for us.  We don't
  // need ordering and the binary equality operations are correct.  So, do
  // NOT set PRAGMA encoding to UTF-16.

  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsPrintfCString(
      // Use a smaller page size to improve perf/footprint; default is too large
      "PRAGMA page_size = %u; "
      // Enable auto_vacuum; this must happen after page_size and before WAL
      "PRAGMA auto_vacuum = INCREMENTAL; "
      "PRAGMA foreign_keys = ON; ",
      kPageSize))));

  // Limit fragmentation by growing the database by many pages at once.
  QM_TRY(QM_OR_ELSE_WARN_IF(
      // Expression.
      MOZ_TO_RESULT(aConn.SetGrowthIncrement(kGrowthSize, ""_ns)),
      // Predicate.
      IsSpecificError<NS_ERROR_FILE_TOO_BIG>,
      // Fallback.
      ErrToDefaultOk<>));

  // Enable WAL journaling.  This must be performed in a separate transaction
  // after changing the page_size and enabling auto_vacuum.
  // Note there is a default journal_size_limit set by mozStorage.
  QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL(nsPrintfCString(
      // WAL journal can grow to given number of *pages*
      "PRAGMA wal_autocheckpoint = %u; "
      // WAL must be enabled at the end to allow page size to be changed, etc.
      "PRAGMA journal_mode = WAL; ",
      kWalAutoCheckpointPages))));

  // Verify that we successfully set the vacuum mode to incremental.  It
  // is very easy to put the database in a state where the auto_vacuum
  // pragma above fails silently.
#ifdef DEBUG
  {
    QM_TRY_INSPECT(const auto& state,
                   quota::CreateAndExecuteSingleStepStatement(
                       aConn, "PRAGMA auto_vacuum;"_ns));

    QM_TRY_INSPECT(const int32_t& mode,
                   MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0));

    // integer value 2 is incremental mode
    QM_TRY(OkIf(mode == 2), NS_ERROR_UNEXPECTED);
  }
#endif

  return NS_OK;
}

Result<CacheId, nsresult> CreateCacheId(mozIStorageConnection& aConn) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY(MOZ_TO_RESULT(
      aConn.ExecuteSimpleSQL("INSERT INTO caches DEFAULT VALUES;"_ns)));

  QM_TRY_INSPECT(const auto& state,
                 quota::CreateAndExecuteSingleStepStatement<
                     quota::SingleStepResult::ReturnNullIfNoResult>(
                     aConn, "SELECT last_insert_rowid()"_ns));

  QM_TRY(OkIf(state), Err(NS_ERROR_UNEXPECTED));

  QM_TRY_INSPECT(const CacheId& id,
                 MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt64, 0));

  return id;
}

Result<DeletionInfo, nsresult> DeleteCacheId(mozIStorageConnection& aConn,
                                             CacheId aCacheId) {
  MOZ_ASSERT(!NS_IsMainThread());

  // XXX only deletedBodyIdList needs to be non-const
  QM_TRY_UNWRAP(
      (auto [deletedBodyIdList, deletedSecurityIdList, deletedPaddingSize]),
      DeleteAllCacheEntries(aConn, aCacheId));

  QM_TRY(MOZ_TO_RESULT(DeleteSecurityInfoList(aConn, deletedSecurityIdList)));

  // Delete the remainder of the cache using cascade semantics.
  QM_TRY_INSPECT(const auto& state,
                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
                     "DELETE FROM caches WHERE id=:id;"_ns));

  QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("id"_ns, aCacheId)));

  QM_TRY(MOZ_TO_RESULT(state->Execute()));

  return DeletionInfo{std::move(deletedBodyIdList), deletedPaddingSize};
}

Result<AutoTArray<CacheId, 8>, nsresult> FindOrphanedCacheIds(
    mozIStorageConnection& aConn) {
  QM_TRY_INSPECT(const auto& state,
                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
                     "SELECT id FROM caches "
                     "WHERE id NOT IN (SELECT cache_id from storage);"_ns));

  QM_TRY_RETURN(
      (quota::CollectElementsWhileHasResultTyped<AutoTArray<CacheId, 8>>(
          *state, [](auto& stmt) {
            QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0));
          })));
}

Result<int64_t, nsresult> FindOverallPaddingSize(mozIStorageConnection& aConn) {
  QM_TRY_INSPECT(const auto& state,
                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
                     "SELECT response_padding_size FROM entries "
                     "WHERE response_padding_size IS NOT NULL;"_ns));

  int64_t overallPaddingSize = 0;

  QM_TRY(quota::CollectWhileHasResult(
      *state, [&overallPaddingSize](auto& stmt) -> Result<Ok, nsresult> {
        QM_TRY_INSPECT(const int64_t& padding_size,
                       MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0));

        MOZ_DIAGNOSTIC_ASSERT(padding_size >= 0);
        MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - padding_size >= overallPaddingSize);
        overallPaddingSize += padding_size;

        return Ok{};
      }));

  return overallPaddingSize;
}

Result<int64_t, nsresult> GetTotalDiskUsage(mozIStorageConnection& aConn) {
  QM_TRY_INSPECT(
      const auto& state,
      quota::CreateAndExecuteSingleStepStatement(
          aConn, "SELECT total_disk_usage FROM usage_info WHERE id = 1;"_ns));

  QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt64, 0));
}

Result<nsTArray<nsID>, nsresult> GetKnownBodyIds(mozIStorageConnection& aConn) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY_INSPECT(
      const auto& state,
      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
          nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
          "SELECT request_body_id, response_body_id FROM entries;"_ns));

  AutoTArray<nsID, 64> idList;

  QM_TRY(quota::CollectWhileHasResult(
      *state, [&idList](auto& stmt) -> Result<Ok, nsresult> {
        // extract 0 to 2 nsID structs per row
        for (uint32_t i = 0; i < 2; ++i) {
          QM_TRY_INSPECT(const bool& isNull,
                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, i));

          if (!isNull) {
            QM_TRY_INSPECT(const auto& id, ExtractId(stmt, i));

            idList.AppendElement(id);
          }
        }

        return Ok{};
      }));

  return std::move(idList);
}

Result<Maybe<SavedResponse>, nsresult> CacheMatch(
    mozIStorageConnection& aConn, CacheId aCacheId,
    const CacheRequest& aRequest, const CacheQueryParams& aParams) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY_INSPECT(const auto& matches,
                 QueryCache(aConn, aCacheId, aRequest, aParams, 1));

  if (matches.IsEmpty()) {
    return Maybe<SavedResponse>();
  }

  QM_TRY_UNWRAP(auto response, ReadResponse(aConn, matches[0]));

  response.mCacheId = aCacheId;

  return Some(std::move(response));
}

Result<nsTArray<SavedResponse>, nsresult> CacheMatchAll(
    mozIStorageConnection& aConn, CacheId aCacheId,
    const Maybe<CacheRequest>& aMaybeRequest, const CacheQueryParams& aParams) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY_INSPECT(
      const auto& matches, ([&aConn, aCacheId, &aMaybeRequest, &aParams] {
        if (aMaybeRequest.isNothing()) {
          QM_TRY_RETURN(QueryAll(aConn, aCacheId));
        }

        QM_TRY_RETURN(
            QueryCache(aConn, aCacheId, aMaybeRequest.ref(), aParams));
      }()));

  // TODO: replace this with a bulk load using SQL IN clause (bug 1110458)
  QM_TRY_RETURN(TransformIntoNewArrayAbortOnErr(
      matches,
      [&aConn, aCacheId](const auto match) -> Result<SavedResponse, nsresult> {
        QM_TRY_UNWRAP(auto savedResponse, ReadResponse(aConn, match));

        savedResponse.mCacheId = aCacheId;
        return savedResponse;
      },
      fallible));
}

Result<DeletionInfo, nsresult> CachePut(mozIStorageConnection& aConn,
                                        CacheId aCacheId,
                                        const CacheRequest& aRequest,
                                        const nsID* aRequestBodyId,
                                        const CacheResponse& aResponse,
                                        const nsID* aResponseBodyId) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY_INSPECT(
      const auto& matches,
      QueryCache(aConn, aCacheId, aRequest,
                 CacheQueryParams(falsefalsefalsefalse, u""_ns)));

  // XXX only deletedBodyIdList needs to be non-const
  QM_TRY_UNWRAP(
      (auto [deletedBodyIdList, deletedSecurityIdList, deletedPaddingSize]),
      DeleteEntries(aConn, matches));

  QM_TRY(MOZ_TO_RESULT(InsertEntry(aConn, aCacheId, aRequest, aRequestBodyId,
                                   aResponse, aResponseBodyId)));

  // Delete the security values after doing the insert to avoid churning
  // the security table when its not necessary.
  QM_TRY(MOZ_TO_RESULT(DeleteSecurityInfoList(aConn, deletedSecurityIdList)));

  return DeletionInfo{std::move(deletedBodyIdList), deletedPaddingSize};
}

Result<Maybe<DeletionInfo>, nsresult> CacheDelete(
    mozIStorageConnection& aConn, CacheId aCacheId,
    const CacheRequest& aRequest, const CacheQueryParams& aParams) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY_INSPECT(const auto& matches,
                 QueryCache(aConn, aCacheId, aRequest, aParams));

  if (matches.IsEmpty()) {
    return Maybe<DeletionInfo>();
  }

  // XXX only deletedBodyIdList needs to be non-const
  QM_TRY_UNWRAP(
      (auto [deletedBodyIdList, deletedSecurityIdList, deletedPaddingSize]),
      DeleteEntries(aConn, matches));

  QM_TRY(MOZ_TO_RESULT(DeleteSecurityInfoList(aConn, deletedSecurityIdList)));

  return Some(DeletionInfo{std::move(deletedBodyIdList), deletedPaddingSize});
}

Result<nsTArray<SavedRequest>, nsresult> CacheKeys(
    mozIStorageConnection& aConn, CacheId aCacheId,
    const Maybe<CacheRequest>& aMaybeRequest, const CacheQueryParams& aParams) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY_INSPECT(
      const auto& matches, ([&aConn, aCacheId, &aMaybeRequest, &aParams] {
        if (aMaybeRequest.isNothing()) {
          QM_TRY_RETURN(QueryAll(aConn, aCacheId));
        }

        QM_TRY_RETURN(
            QueryCache(aConn, aCacheId, aMaybeRequest.ref(), aParams));
      }()));

  // TODO: replace this with a bulk load using SQL IN clause (bug 1110458)
  QM_TRY_RETURN(TransformIntoNewArrayAbortOnErr(
      matches,
      [&aConn, aCacheId](const auto match) -> Result<SavedRequest, nsresult> {
        QM_TRY_UNWRAP(auto savedRequest, ReadRequest(aConn, match));

        savedRequest.mCacheId = aCacheId;
        return savedRequest;
      },
      fallible));
}

Result<Maybe<SavedResponse>, nsresult> StorageMatch(
    mozIStorageConnection& aConn, Namespace aNamespace,
    const CacheRequest& aRequest, const CacheQueryParams& aParams) {
  MOZ_ASSERT(!NS_IsMainThread());

  // If we are given a cache to check, then simply find its cache ID
  // and perform the match.
  if (aParams.cacheNameSet()) {
    QM_TRY_INSPECT(const auto& maybeCacheId,
                   StorageGetCacheId(aConn, aNamespace, aParams.cacheName()));
    if (maybeCacheId.isNothing()) {
      return Maybe<SavedResponse>();
    }

    return CacheMatch(aConn, maybeCacheId.ref(), aRequest, aParams);
  }

  // Otherwise we need to get a list of all the cache IDs in this namespace.

  QM_TRY_INSPECT(const auto& state,
                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
                     "SELECT cache_id FROM storage WHERE "
                     "namespace=:namespace ORDER BY rowid;"_ns));

  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace)));

  QM_TRY_INSPECT(
      const auto& cacheIdList,
      (quota::CollectElementsWhileHasResultTyped<AutoTArray<CacheId, 32>>(
          *state, [](auto& stmt) {
            QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 0));
          })));

  // Now try to find a match in each cache in order
  for (const auto cacheId : cacheIdList) {
    QM_TRY_UNWRAP(auto matchedResponse,
                  CacheMatch(aConn, cacheId, aRequest, aParams));

    if (matchedResponse.isSome()) {
      return matchedResponse;
    }
  }

  return Maybe<SavedResponse>();
}

Result<Maybe<CacheId>, nsresult> StorageGetCacheId(mozIStorageConnection& aConn,
                                                   Namespace aNamespace,
                                                   const nsAString& aKey) {
  MOZ_ASSERT(!NS_IsMainThread());

  // How we constrain the key column depends on the value of our key.  Use
  // a format string for the query and let CreateAndBindKeyStatement() fill
  // it in for us.
  const charconst query =
      "SELECT cache_id FROM storage "
      "WHERE namespace=:namespace AND %s "
      "ORDER BY rowid;";

  QM_TRY_INSPECT(const auto& state,
                 CreateAndBindKeyStatement(aConn, query, aKey));

  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace)));

  QM_TRY_INSPECT(const bool& hasMoreData,
                 MOZ_TO_RESULT_INVOKE_MEMBER(*state, ExecuteStep));

  if (!hasMoreData) {
    return Maybe<CacheId>();
  }

  QM_TRY_RETURN(
      MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt64, 0).map(Some<CacheId>));
}

nsresult StoragePutCache(mozIStorageConnection& aConn, Namespace aNamespace,
                         const nsAString& aKey, CacheId aCacheId) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY_INSPECT(const auto& state,
                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
                     "INSERT INTO storage (namespace, key, cache_id) "
                     "VALUES (:namespace, :key, :cache_id);"_ns));

  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace)));
  QM_TRY(MOZ_TO_RESULT(state->BindStringAsBlobByName("key"_ns, aKey)));
  QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId)));
  QM_TRY(MOZ_TO_RESULT(state->Execute()));

  return NS_OK;
}

nsresult StorageForgetCache(mozIStorageConnection& aConn, Namespace aNamespace,
                            const nsAString& aKey) {
  MOZ_ASSERT(!NS_IsMainThread());

  // How we constrain the key column depends on the value of our key.  Use
  // a format string for the query and let CreateAndBindKeyStatement() fill
  // it in for us.
  const charconst query =
      "DELETE FROM storage WHERE namespace=:namespace AND %s;";

  QM_TRY_INSPECT(const auto& state,
                 CreateAndBindKeyStatement(aConn, query, aKey));

  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace)));

  QM_TRY(MOZ_TO_RESULT(state->Execute()));

  return NS_OK;
}

Result<nsTArray<nsString>, nsresult> StorageGetKeys(
    mozIStorageConnection& aConn, Namespace aNamespace) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY_INSPECT(
      const auto& state,
      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
          nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
          "SELECT key FROM storage WHERE namespace=:namespace ORDER BY rowid;"_ns));

  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("namespace"_ns, aNamespace)));

  QM_TRY_RETURN(quota::CollectElementsWhileHasResult(*state, [](auto& stmt) {
    QM_TRY_RETURN(
        MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsString, stmt, GetBlobAsString, 0));
  }));
}

namespace {

Result<EntryIds, nsresult> QueryAll(mozIStorageConnection& aConn,
                                    CacheId aCacheId) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY_INSPECT(
      const auto& state,
      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
          nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
          "SELECT id FROM entries WHERE cache_id=:cache_id ORDER BY id;"_ns));

  QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId)));

  QM_TRY_RETURN((quota::CollectElementsWhileHasResultTyped<EntryIds>(
      *state, [](auto& stmt) {
        QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 0));
      })));
}

Result<EntryIds, nsresult> QueryCache(mozIStorageConnection& aConn,
                                      CacheId aCacheId,
                                      const CacheRequest& aRequest,
                                      const CacheQueryParams& aParams,
                                      uint32_t aMaxResults) {
  MOZ_ASSERT(!NS_IsMainThread());
  MOZ_DIAGNOSTIC_ASSERT(aMaxResults > 0);

  if (!aParams.ignoreMethod() &&
      !aRequest.method().LowerCaseEqualsLiteral("get")) {
    return Result<EntryIds, nsresult>{std::in_place};
  }

  nsAutoCString query(
      "SELECT id, COUNT(response_headers.name) AS vary_count, response_type "
      "FROM entries "
      "LEFT OUTER JOIN response_headers ON "
      "entries.id=response_headers.entry_id "
      "AND response_headers.name='vary' COLLATE NOCASE "
      "WHERE entries.cache_id=:cache_id "
      "AND entries.request_url_no_query_hash=:url_no_query_hash ");

  if (!aParams.ignoreSearch()) {
    query.AppendLiteral("AND entries.request_url_query_hash=:url_query_hash ");
  }

  query.AppendLiteral("AND entries.request_url_no_query=:url_no_query ");

  if (!aParams.ignoreSearch()) {
    query.AppendLiteral("AND entries.request_url_query=:url_query ");
  }

  query.AppendLiteral("GROUP BY entries.id ORDER BY entries.id;");

  QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                                        nsCOMPtr<mozIStorageStatement>, aConn,
                                        CreateStatement, query));

  QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId)));

  QM_TRY_INSPECT(const auto& crypto,
                 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsICryptoHash>,
                                         MOZ_SELECT_OVERLOAD(do_CreateInstance),
                                         NS_CRYPTO_HASH_CONTRACTID));

  QM_TRY_INSPECT(const auto& urlWithoutQueryHash,
                 HashCString(*crypto, aRequest.urlWithoutQuery()));

  QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName("url_no_query_hash"_ns,
                                                         urlWithoutQueryHash)));

  if (!aParams.ignoreSearch()) {
    QM_TRY_INSPECT(const auto& urlQueryHash,
                   HashCString(*crypto, aRequest.urlQuery()));

    QM_TRY(MOZ_TO_RESULT(
        state->BindUTF8StringAsBlobByName("url_query_hash"_ns, urlQueryHash)));
  }

  QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringByName(
      "url_no_query"_ns, aRequest.urlWithoutQuery())));

  if (!aParams.ignoreSearch()) {
    QM_TRY(MOZ_TO_RESULT(
        state->BindUTF8StringByName("url_query"_ns, aRequest.urlQuery())));
  }

  EntryIds entryIdList;

  QM_TRY(CollectWhile(
      [&state, &entryIdList, aMaxResults]() -> Result<bool, nsresult> {
        if (entryIdList.Length() == aMaxResults) {
          return false;
        }
        QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(state, ExecuteStep));
      },
      [&state, &entryIdList, &aParams, &aConn,
       &aRequest]() -> Result<Ok, nsresult> {
        QM_TRY_INSPECT(const EntryId& entryId,
                       MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 0));

        QM_TRY_INSPECT(const int32_t& varyCount,
                       MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 1));

        QM_TRY_INSPECT(const int32_t& responseType,
                       MOZ_TO_RESULT_INVOKE_MEMBER(state, GetInt32, 2));

        auto ignoreVary =
            aParams.ignoreVary() ||
            responseType == static_cast<int>(ResponseType::Opaque);

        if (!ignoreVary && varyCount > 0) {
          QM_TRY_INSPECT(const bool& matchedByVary,
                         MatchByVaryHeader(aConn, aRequest, entryId));
          if (!matchedByVary) {
            return Ok{};
          }
        }

        entryIdList.AppendElement(entryId);

        return Ok{};
      }));

  return entryIdList;
}

Result<bool, nsresult> MatchByVaryHeader(mozIStorageConnection& aConn,
                                         const CacheRequest& aRequest,
                                         EntryId entryId) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY_INSPECT(
      const auto& varyValues,
      ([&aConn, entryId]() -> Result<AutoTArray<nsCString, 8>, nsresult> {
        QM_TRY_INSPECT(
            const auto& state,
            MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
                "SELECT value FROM response_headers "
                "WHERE name='vary' COLLATE NOCASE "
                "AND entry_id=:entry_id;"_ns));

        QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, entryId)));

        QM_TRY_RETURN((
            quota::CollectElementsWhileHasResultTyped<AutoTArray<nsCString, 8>>(
                *state, [](auto& stmt) {
                  QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                      nsCString, stmt, GetUTF8String, 0));
                })));
      }()));

  // Should not have called this function if this was not the case
  MOZ_DIAGNOSTIC_ASSERT(!varyValues.IsEmpty());

  QM_TRY_INSPECT(const auto& state,
                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
                     "SELECT name, value FROM request_headers "
                     "WHERE entry_id=:entry_id;"_ns));

  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("entry_id"_ns, entryId)));

  RefPtr<InternalHeaders> cachedHeaders =
      new InternalHeaders(HeadersGuardEnum::None);

  QM_TRY(quota::CollectWhileHasResult(
      *state, [&cachedHeaders](auto& stmt) -> Result<Ok, nsresult> {
        QM_TRY_INSPECT(const auto& name,
                       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
                                                         GetUTF8String, 0));
        QM_TRY_INSPECT(const auto& value,
                       MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, stmt,
                                                         GetUTF8String, 1));

        ErrorResult errorResult;

        cachedHeaders->Append(name, value, errorResult);
        if (errorResult.Failed()) {
          return Err(errorResult.StealNSResult());
        }

        return Ok{};
      }));

  RefPtr<InternalHeaders> queryHeaders =
      TypeUtils::ToInternalHeaders(aRequest.headers());

  // Assume the vary headers match until we find a conflict
  bool varyHeadersMatch = true;

  for (const auto& varyValue : varyValues) {
    // Extract the header names inside the Vary header value.
    bool bailOut = false;
    for (const nsACString& header :
         nsCCharSeparatedTokenizer(varyValue, NS_HTTP_HEADER_SEP).ToRange()) {
      MOZ_DIAGNOSTIC_ASSERT(!header.EqualsLiteral("*"),
                            "We should have already caught this in "
                            "TypeUtils::ToPCacheResponseWithoutBody()");

      ErrorResult errorResult;
      nsAutoCString queryValue;
      queryHeaders->Get(header, queryValue, errorResult);
      if (errorResult.Failed()) {
        errorResult.SuppressException();
        MOZ_DIAGNOSTIC_ASSERT(queryValue.IsEmpty());
      }

      nsAutoCString cachedValue;
      cachedHeaders->Get(header, cachedValue, errorResult);
      if (errorResult.Failed()) {
        errorResult.SuppressException();
        MOZ_DIAGNOSTIC_ASSERT(cachedValue.IsEmpty());
      }

      if (queryValue != cachedValue) {
        varyHeadersMatch = false;
        bailOut = true;
        break;
      }
    }

    if (bailOut) {
      break;
    }
  }

  return varyHeadersMatch;
}

static nsresult SelectAndDeleteEntriesInternal(
    mozIStorageConnection& aConn, const Span<const EntryId>& aEntryIdList,
    nsTArray<nsID>& aDeletedBodyIdListOut,
    nsTArray<IdCount>& aDeletedSecurityIdListOut,
    int64_t& aDeletedPaddingSizeOut) {
  nsAutoCString query(
      "SELECT "
      "request_body_id, "
      "response_body_id, "
      "response_security_info_id, "
      "response_padding_size "
      "FROM entries WHERE id IN (");

  AppendListParamsToQuery(query, aEntryIdList.Length());
  query.AppendLiteral(")");

  QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                                        nsCOMPtr<mozIStorageStatement>, aConn,
                                        CreateStatement, query));

  QM_TRY(MOZ_TO_RESULT(BindListParamsToQuery(*state, aEntryIdList)));

  int64_t overallPaddingSize = 0;

  QM_TRY(quota::CollectWhileHasResult(
      *state,
      [&overallPaddingSize, &aDeletedBodyIdListOut,
       &aDeletedSecurityIdListOut](auto& stmt) -> Result<Ok, nsresult> {
        // extract 0 to 2 nsID structs per row
        for (uint32_t i = 0; i < 2; ++i) {
          QM_TRY_INSPECT(const bool& isNull,
                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, i));

          if (!isNull) {
            QM_TRY_INSPECT(const auto& id, ExtractId(stmt, i));

            aDeletedBodyIdListOut.AppendElement(id);
          }
        }

        {  // and then a possible third entry for the security id
          QM_TRY_INSPECT(const bool& isNull,
                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, 2));

          if (!isNull) {
            QM_TRY_INSPECT(const int32_t& securityId,
                           MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 2));

            // XXXtt: Consider using map for aDeletedSecuityIdListOut.
            auto foundIt =
                std::find_if(aDeletedSecurityIdListOut.begin(),
                             aDeletedSecurityIdListOut.end(),
                             [securityId](const auto& deletedSecurityId) {
                               return deletedSecurityId.mId == securityId;
                             });

            if (foundIt == aDeletedSecurityIdListOut.end()) {
              // Add a new entry for this ID with a count of 1, if it's not in
              // the list
              aDeletedSecurityIdListOut.AppendElement(IdCount(securityId));
            } else {
              // Otherwise, increment the count for this ID
              foundIt->mCount += 1;
            }
          }
        }

        {
          // It's possible to have null padding size for non-opaque response
          QM_TRY_INSPECT(const bool& isNull,
                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, 3));

          if (!isNull) {
            QM_TRY_INSPECT(const int64_t& paddingSize,
                           MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 3));

            MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0);
            MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - overallPaddingSize >=
                                  paddingSize);
            overallPaddingSize += paddingSize;
          }
        }

        return Ok{};
      }));

  aDeletedPaddingSizeOut += overallPaddingSize;

  // Dependent records removed via ON DELETE CASCADE

  query = "DELETE FROM entries WHERE id IN ("_ns;
  AppendListParamsToQuery(query, aEntryIdList.Length());
  query.AppendLiteral(")");

  {
    QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                                          nsCOMPtr<mozIStorageStatement>, aConn,
                                          CreateStatement, query));

    QM_TRY(MOZ_TO_RESULT(BindListParamsToQuery(*state, aEntryIdList)));

    QM_TRY(MOZ_TO_RESULT(state->Execute()));
  }

  return NS_OK;
}

static nsresult DeleteEntriesInternal(
    mozIStorageConnection& aConn, const nsTArray<EntryId>& aEntryIdList,
    nsTArray<nsID>& aDeletedBodyIdListOut,
    nsTArray<IdCount>& aDeletedSecurityIdListOut,
    int64_t& aDeletedPaddingSizeOut, uint32_t aPos, uint32_t aLen) {
  MOZ_ASSERT(!NS_IsMainThread());

  if (aEntryIdList.IsEmpty()) {
    return NS_OK;
  }

  MOZ_DIAGNOSTIC_ASSERT(aPos < aEntryIdList.Length());

  auto remaining = aLen;
  uint32_t currPos = 0;

  do {
    // Sqlite limits the number of entries allowed for an IN clause,
    // so split up larger operations.
    auto currLen = std::min(kMaxEntriesPerStatement, remaining);

    SelectAndDeleteEntriesInternal(
        aConn, Span<const EntryId>(aEntryIdList.Elements() + currPos, currLen),
        aDeletedBodyIdListOut, aDeletedSecurityIdListOut,
        aDeletedPaddingSizeOut);

    remaining -= currLen;
    currPos += currLen;

  } while (remaining > 0);

  return NS_OK;
}

Result<std::tuple<nsTArray<nsID>, AutoTArray<IdCount, 16>, int64_t>, nsresult>
DeleteEntries(mozIStorageConnection& aConn,
              const nsTArray<EntryId>& aEntryIdList) {
  auto result =
      std::make_tuple(nsTArray<nsID>{}, AutoTArray<IdCount, 16>{}, int64_t{0});

  QM_TRY(MOZ_TO_RESULT(DeleteEntriesInternal(
      aConn, aEntryIdList, std::get<0>(result), std::get<1>(result),
      std::get<2>(result), 0, aEntryIdList.Length())));

  return result;
}

Result<std::tuple<nsTArray<nsID>, AutoTArray<IdCount, 16>, int64_t>, nsresult>
DeleteAllCacheEntries(mozIStorageConnection& aConn, CacheId& aCacheId) {
  auto result =
      std::make_tuple(nsTArray<nsID>{}, AutoTArray<IdCount, 16>{}, int64_t{0});
  auto& deletedBodyIdList = std::get<0>(result);
  auto& deletedSecurityIdList = std::get<1>(result);
  auto& deletedPaddingSize = std::get<2>(result);

  // XXX: We could create a query string with aggregation that would generate
  // a single summary result such that we don't have to go through each row
  // just to aggregate the result. This method could become much more
  // performant.
  //
  // The columns could look like:
  //
  // GROUP_CONCAT(request_body_id || ',' || response_body_id),
  // GROUP_CONCAT(response_security_info_id),
  // SUM(response_padding_size)
  //
  // strtok the result row to generate the desired output to fill-in
  // deletedBodyIdList, deletedSecurityIdList and deletedPaddingSize
  //
  // I am not sure about the memory requirements for such operation;
  // it will all depend upon the result filtered by the `cache_id`.
  nsAutoCString query(
      "SELECT "
      "request_body_id, "
      "response_body_id, "
      "response_security_info_id, "
      "response_padding_size "
      "FROM entries WHERE cache_id=:cache_id ORDER BY id;"_ns);

  QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                                        nsCOMPtr<mozIStorageStatement>, aConn,
                                        CreateStatement, query));

  QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId)));

  QM_TRY(quota::CollectWhileHasResult(
      *state,
      [&deletedPaddingSize, &deletedBodyIdList,
       &deletedSecurityIdList](auto& stmt) -> Result<Ok, nsresult> {
        // extract 0 to 2 nsID structs per row
        for (uint32_t i = 0; i < 2; ++i) {
          QM_TRY_INSPECT(const bool& isNull,
                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, i));

          if (!isNull) {
            QM_TRY_INSPECT(const auto& id, ExtractId(stmt, i));

            deletedBodyIdList.AppendElement(id);
          }
        }

        {  // and then a possible third entry for the security id
          QM_TRY_INSPECT(const bool& isNull,
                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, 2));

          if (!isNull) {
            QM_TRY_INSPECT(const int32_t& securityId,
                           MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt32, 2));

            // XXXtt: Consider using map for aDeletedSecuityIdListOut.
            auto foundIt = std::find_if(
                deletedSecurityIdList.begin(), deletedSecurityIdList.end(),
                [securityId](const auto& deletedSecurityId) {
                  return deletedSecurityId.mId == securityId;
                });

            if (foundIt == deletedSecurityIdList.end()) {
              // Add a new entry for this ID with a count of 1, if it's not in
              // the list
              deletedSecurityIdList.AppendElement(IdCount(securityId));
            } else {
              // Otherwise, increment the count for this ID
              foundIt->mCount += 1;
            }
          }
        }

        {
          // It's possible to have null padding size for non-opaque response
          QM_TRY_INSPECT(const bool& isNull,
                         MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetIsNull, 3));

          if (!isNull) {
            QM_TRY_INSPECT(const int64_t& paddingSize,
                           MOZ_TO_RESULT_INVOKE_MEMBER(stmt, GetInt64, 3));

            MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0);

            // Assert overflow
            MOZ_DIAGNOSTIC_ASSERT(INT64_MAX - deletedPaddingSize >=
                                  paddingSize);

            deletedPaddingSize += paddingSize;
          }
        }

        return Ok{};
      }));

  // Dependent records removed via ON DELETE CASCADE

  query = "DELETE FROM entries WHERE cache_id=:cache_id"_ns;

  {
    QM_TRY_INSPECT(const auto& state, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                                          nsCOMPtr<mozIStorageStatement>, aConn,
                                          CreateStatement, query));

    QM_TRY(MOZ_TO_RESULT(state->BindInt64ByName("cache_id"_ns, aCacheId)));
    QM_TRY(MOZ_TO_RESULT(state->Execute()));
  }

  return result;
}

Result<int32_t, nsresult> InsertSecurityInfo(
    mozIStorageConnection& aConn, nsICryptoHash& aCrypto,
    nsITransportSecurityInfo* aSecurityInfo) {
  MOZ_DIAGNOSTIC_ASSERT(aSecurityInfo);
  if (!aSecurityInfo) {
    return Err(NS_ERROR_FAILURE);
  }
  nsCString data;
  nsresult rv = aSecurityInfo->ToString(data);
  if (NS_FAILED(rv)) {
    return Err(rv);
  }

  // We want to use an index to find existing security blobs, but indexing
  // the full blob would be quite expensive.  Instead, we index a small
  // hash value.  Calculate this hash as the first 8 bytes of the SHA1 of
  // the full data.
  QM_TRY_INSPECT(const auto& hash, HashCString(aCrypto, data));

  // Next, search for an existing entry for this blob by comparing the hash
  // value first and then the full data.  SQLite is smart enough to use
  // the index on the hash to search the table before doing the expensive
  // comparison of the large data column.  (This was verified with EXPLAIN.)
  QM_TRY_INSPECT(
      const auto& selectStmt,
      quota::CreateAndExecuteSingleStepStatement<
          quota::SingleStepResult::ReturnNullIfNoResult>(
          aConn,
          // Note that hash and data are blobs, but we can use = here since the
          // columns are NOT NULL.
          "SELECT id, refcount FROM security_info WHERE hash=:hash AND "
          "data=:data;"_ns,
          [&hash, &data](auto& state) -> Result<Ok, nsresult> {
            QM_TRY(MOZ_TO_RESULT(
                state.BindUTF8StringAsBlobByName("hash"_ns, hash)));
            QM_TRY(MOZ_TO_RESULT(
                state.BindUTF8StringAsBlobByName("data"_ns, data)));

            return Ok{};
          }));

  // This security info blob is already in the database
  if (selectStmt) {
    // get the existing security blob id to return
    QM_TRY_INSPECT(const int32_t& id,
                   MOZ_TO_RESULT_INVOKE_MEMBER(selectStmt, GetInt32, 0));
    QM_TRY_INSPECT(const int32_t& refcount,
                   MOZ_TO_RESULT_INVOKE_MEMBER(selectStmt, GetInt32, 1));

    // But first, update the refcount in the database.
    QM_TRY_INSPECT(
        const auto& state,
        MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
            nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
            "UPDATE security_info SET refcount=:refcount WHERE id=:id;"_ns));

    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("refcount"_ns, refcount + 1)));
    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("id"_ns, id)));
    QM_TRY(MOZ_TO_RESULT(state->Execute()));

    return id;
  }

  // This is a new security info blob.  Create a new row in the security table
  // with an initial refcount of 1.
  QM_TRY_INSPECT(const auto& state,
                 MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                     nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
                     "INSERT INTO security_info (hash, data, refcount) "
                     "VALUES (:hash, :data, 1);"_ns));

  QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName("hash"_ns, hash)));
  QM_TRY(MOZ_TO_RESULT(state->BindUTF8StringAsBlobByName("data"_ns, data)));
  QM_TRY(MOZ_TO_RESULT(state->Execute()));

  {
    QM_TRY_INSPECT(const auto& state,
                   quota::CreateAndExecuteSingleStepStatement(
                       aConn, "SELECT last_insert_rowid()"_ns));

    QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0));
  }
}

nsresult DeleteSecurityInfo(mozIStorageConnection& aConn, int32_t aId,
                            int32_t aCount) {
  // First, we need to determine the current refcount for this security blob.
  QM_TRY_INSPECT(
      const int32_t& refcount, ([&aConn, aId]() -> Result<int32_t, nsresult> {
        QM_TRY_INSPECT(
            const auto& state,
            quota::CreateAndExecuteSingleStepStatement(
                aConn, "SELECT refcount FROM security_info WHERE id=:id;"_ns,
                [aId](auto& state) -> Result<Ok, nsresult> {
                  QM_TRY(MOZ_TO_RESULT(state.BindInt32ByName("id"_ns, aId)));
                  return Ok{};
                }));

        QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE_MEMBER(*state, GetInt32, 0));
      }()));

  MOZ_ASSERT_DEBUG_OR_FUZZING(refcount >= aCount);

  // Next, calculate the new refcount
  int32_t newCount = refcount - aCount;

  // If the last reference to this security blob was removed we can
  // just remove the entire row.
  if (newCount == 0) {
    QM_TRY_INSPECT(const auto& state,
                   MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
                       nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
                       "DELETE FROM security_info WHERE id=:id;"_ns));

    QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("id"_ns, aId)));
    QM_TRY(MOZ_TO_RESULT(state->Execute()));

    return NS_OK;
  }

  // Otherwise update the refcount in the table to reflect the reduced
  // number of references to the security blob.
  QM_TRY_INSPECT(
      const auto& state,
      MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
          nsCOMPtr<mozIStorageStatement>, aConn, CreateStatement,
          "UPDATE security_info SET refcount=:refcount WHERE id=:id;"_ns));

  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("refcount"_ns, newCount)));
  QM_TRY(MOZ_TO_RESULT(state->BindInt32ByName("id"_ns, aId)));
  QM_TRY(MOZ_TO_RESULT(state->Execute()));

  return NS_OK;
}

nsresult DeleteSecurityInfoList(
    mozIStorageConnection& aConn,
    const nsTArray<IdCount>& aDeletedStorageIdList) {
  for (const auto& deletedStorageId : aDeletedStorageIdList) {
    QM_TRY(MOZ_TO_RESULT(DeleteSecurityInfo(aConn, deletedStorageId.mId,
                                            deletedStorageId.mCount)));
  }

  return NS_OK;
}

nsresult InsertEntry(mozIStorageConnection& aConn, CacheId aCacheId,
                     const CacheRequest& aRequest, const nsID* aRequestBodyId,
                     const CacheResponse& aResponse,
                     const nsID* aResponseBodyId) {
  MOZ_ASSERT(!NS_IsMainThread());

  QM_TRY_INSPECT(const auto& crypto,
                 MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsICryptoHash>,
                                         MOZ_SELECT_OVERLOAD(do_CreateInstance),
--> --------------------

--> maximum size reached

--> --------------------

97%


¤ Dauer der Verarbeitung: 0.22 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.