--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -30,18 +30,54 @@
#include "nsTArray.h"
namespace mozilla {
namespace dom {
namespace cache {
namespace 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 = 26;
+const int32_t kLatestSchemaVersion = 27;
// ---------
// 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.
@@ -351,16 +387,18 @@ static nsresult BindId(mozIStorageStatem
static nsresult ExtractId(mozIStorageStatement* aState, uint32_t aPos,
nsID* aIdOut);
static nsresult CreateAndBindKeyStatement(mozIStorageConnection* aConn,
const char* aQueryFormat,
const nsAString& aKey,
mozIStorageStatement** aStateOut);
static nsresult HashCString(nsICryptoHash* aCrypto, const nsACString& aIn,
nsACString& aOut);
+nsresult GetEffectiveSchemaVersion(mozIStorageConnection* aConn,
+ int32_t& schemaVersion);
nsresult Validate(mozIStorageConnection* aConn);
nsresult Migrate(mozIStorageConnection* aConn);
} // namespace
class MOZ_RAII AutoDisableForeignKeyChecking
{
public:
explicit AutoDisableForeignKeyChecking(mozIStorageConnection* aConn)
@@ -407,17 +445,17 @@ private:
nsresult
CreateOrMigrateSchema(mozIStorageConnection* aConn)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(aConn);
int32_t schemaVersion;
- nsresult rv = aConn->GetSchemaVersion(&schemaVersion);
+ nsresult rv = GetEffectiveSchemaVersion(aConn, schemaVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (schemaVersion == kLatestSchemaVersion) {
// We already have the correct schema version. Validate it matches
// our expected schema and then proceed.
rv = Validate(aConn);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@@ -468,20 +506,20 @@ CreateOrMigrateSchema(mozIStorageConnect
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableResponseUrlList));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableStorage));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
- rv = aConn->SetSchemaVersion(kLatestSchemaVersion);
+ rv = aConn->SetSchemaVersion(kHackyDowngradeSchemaVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
- rv = aConn->GetSchemaVersion(&schemaVersion);
+ rv = GetEffectiveSchemaVersion(aConn, schemaVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
rv = Validate(aConn);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = trans.Commit();
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@@ -2482,16 +2520,54 @@ IncrementalVacuum(mozIStorageConnection*
MOZ_ASSERT(freePages <= kMaxFreePages);
#endif
return NS_OK;
}
namespace {
+// Wrapper around mozIStorageConnection::GetSchemaVersion() that compensates
+// for hacky downgrade schema version tricks. See the block comments for
+// kHackyDowngradeSchemaVersion and kHackyPaddingSizePresentVersion.
+nsresult
+GetEffectiveSchemaVersion(mozIStorageConnection* aConn,
+ int32_t& schemaVersion)
+{
+ nsresult rv = aConn->GetSchemaVersion(&schemaVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ if (schemaVersion == kHackyDowngradeSchemaVersion) {
+ // This is the special case. Check for the existence of the
+ // "response_padding_size" colum in table "entries".
+ //
+ // (pragma_table_info is a table-valued function format variant of
+ // "PRAGMA table_info" supported since SQLite 3.16.0. Firefox 53 shipped
+ // was the first release with this functionality, shipping 3.16.2.)
+ nsCOMPtr<mozIStorageStatement> stmt;
+ nsresult rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT name FROM pragma_table_info('entries') WHERE "
+ "name = 'response_padding_size'"
+ ), getter_AddRefs(stmt));
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ // If there are any result rows, then the column is present.
+ bool hasColumn = false;
+ rv = stmt->ExecuteStep(&hasColumn);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+
+ if (hasColumn) {
+ schemaVersion = kHackyPaddingSizePresentVersion;
+ }
+ }
+
+ return NS_OK;
+}
+
+
#ifdef DEBUG
struct Expect
{
// Expect exact SQL
Expect(const char* aName, const char* aType, const char* aSql)
: mName(aName)
, mType(aType)
, mSql(aSql)
@@ -2511,17 +2587,17 @@ struct Expect
const bool mIgnoreSql;
};
#endif
nsresult
Validate(mozIStorageConnection* aConn)
{
int32_t schemaVersion;
- nsresult rv = aConn->GetSchemaVersion(&schemaVersion);
+ nsresult rv = GetEffectiveSchemaVersion(aConn, schemaVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (NS_WARN_IF(schemaVersion != kLatestSchemaVersion)) {
return NS_ERROR_FAILURE;
}
#ifdef DEBUG
// This is the schema we expect the database at the latest version to
@@ -2617,29 +2693,31 @@ nsresult MigrateFrom17To18(mozIStorageCo
nsresult MigrateFrom18To19(mozIStorageConnection* aConn, bool& aRewriteSchema);
nsresult MigrateFrom19To20(mozIStorageConnection* aConn, bool& aRewriteSchema);
nsresult MigrateFrom20To21(mozIStorageConnection* aConn, bool& aRewriteSchema);
nsresult MigrateFrom21To22(mozIStorageConnection* aConn, bool& aRewriteSchema);
nsresult MigrateFrom22To23(mozIStorageConnection* aConn, bool& aRewriteSchema);
nsresult MigrateFrom23To24(mozIStorageConnection* aConn, bool& aRewriteSchema);
nsresult MigrateFrom24To25(mozIStorageConnection* aConn, bool& aRewriteSchema);
nsresult MigrateFrom25To26(mozIStorageConnection* aConn, bool& aRewriteSchema);
+nsresult MigrateFrom26To27(mozIStorageConnection* aConn, bool& aRewriteSchema);
// Configure migration functions to run for the given starting version.
Migration sMigrationList[] = {
Migration(15, MigrateFrom15To16),
Migration(16, MigrateFrom16To17),
Migration(17, MigrateFrom17To18),
Migration(18, MigrateFrom18To19),
Migration(19, MigrateFrom19To20),
Migration(20, MigrateFrom20To21),
Migration(21, MigrateFrom21To22),
Migration(22, MigrateFrom22To23),
Migration(23, MigrateFrom23To24),
Migration(24, MigrateFrom24To25),
Migration(25, MigrateFrom25To26),
+ Migration(26, MigrateFrom26To27),
};
uint32_t sMigrationListLength = sizeof(sMigrationList) / sizeof(Migration);
nsresult
RewriteEntriesSchema(mozIStorageConnection* aConn)
{
nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"PRAGMA writable_schema = ON"
));
@@ -2668,17 +2746,17 @@ RewriteEntriesSchema(mozIStorageConnecti
nsresult
Migrate(mozIStorageConnection* aConn)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(aConn);
int32_t currentVersion = 0;
- nsresult rv = aConn->GetSchemaVersion(¤tVersion);
+ nsresult rv = GetEffectiveSchemaVersion(aConn, currentVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
bool rewriteSchema = false;
while (currentVersion < kLatestSchemaVersion) {
// Wiping old databases is handled in DBAction because it requires
// making a whole new mozIStorageConnection. Make sure we don't
// accidentally get here for one of those old databases.
@@ -2694,17 +2772,17 @@ Migrate(mozIStorageConnection* aConn)
}
break;
}
}
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
int32_t lastVersion = currentVersion;
#endif
- rv = aConn->GetSchemaVersion(¤tVersion);
+ rv = GetEffectiveSchemaVersion(aConn, currentVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
MOZ_DIAGNOSTIC_ASSERT(currentVersion > lastVersion);
}
// Don't release assert this since people do sometimes share profiles
// across schema versions. Our check in Validate() will catch it.
MOZ_ASSERT(currentVersion == kLatestSchemaVersion);
@@ -3193,13 +3271,23 @@ nsresult MigrateFrom25To26(mozIStorageCo
rv = aConn->SetSchemaVersion(26);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
aRewriteSchema = true;
return rv;
}
+nsresult MigrateFrom26To27(mozIStorageConnection* aConn, bool& aRewriteSchema)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(aConn);
+
+ nsresult rv = aConn->SetSchemaVersion(kHackyDowngradeSchemaVersion);
+ if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
+ return rv;
+}
+
} // anonymous namespace
} // namespace db
} // namespace cache
} // namespace dom
} // namespace mozilla