/* -*- 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 "SchemaUpgrades.h" // local includes #include "ActorsParentCommon.h" #include "DBSchema.h" #include "FileInfoFwd.h" #include "FileManager.h" #include "IndexedDatabase.h" #include "IndexedDBCommon.h" #include "ReportInternalError.h" // global includes #include #include #include #include #include #include "ErrorList.h" #include "GeckoProfiler.h" #include "MainThreadUtils.h" #include "SafeRefPtr.h" #include "js/RootingAPI.h" #include "js/StructuredClone.h" #include "js/Value.h" #include "jsapi.h" #include "mozIStorageConnection.h" #include "mozIStorageFunction.h" #include "mozIStorageStatement.h" #include "mozIStorageValueArray.h" #include "mozStorageHelper.h" #include "mozilla/Assertions.h" #include "mozilla/ErrorResult.h" #include "mozilla/MacroForEach.h" #include "mozilla/Monitor.h" #include "mozilla/OriginAttributes.h" #include "mozilla/RefPtr.h" #include "mozilla/SchedulerGroup.h" #include "mozilla/Span.h" #include "mozilla/TaskCategory.h" #include "mozilla/UniquePtr.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/indexedDB/IDBResult.h" #include "mozilla/dom/indexedDB/Key.h" #include "mozilla/dom/quota/PersistenceType.h" #include "mozilla/dom/quota/QuotaCommon.h" #include "mozilla/fallible.h" #include "mozilla/ipc/BackgroundParent.h" #include "mozilla/mozalloc.h" #include "mozilla/storage/Variant.h" #include "nsCOMPtr.h" #include "nsDebug.h" #include "nsError.h" #include "nsISupports.h" #include "nsIVariant.h" #include "nsLiteralString.h" #include "nsString.h" #include "nsTArray.h" #include "nsTLiteralString.h" #include "nsTStringRepr.h" #include "nsThreadUtils.h" #include "nscore.h" #include "snappy/snappy.h" struct JSContext; class JSObject; #if defined(MOZ_WIDGET_ANDROID) # define IDB_MOBILE #endif namespace mozilla::dom::indexedDB { using mozilla::ipc::IsOnBackgroundThread; using quota::AssertIsOnIOThread; using quota::PERSISTENCE_TYPE_INVALID; namespace { nsresult UpgradeSchemaFrom4To5(mozIStorageConnection& aConnection) { AssertIsOnIOThread(); AUTO_PROFILER_LABEL("UpgradeSchemaFrom4To5", DOM); 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 stmt; rv = aConnection.CreateStatement( "SELECT name, version, dataVersion " "FROM database"_ns, getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsString name; int32_t intVersion; int64_t dataVersion; { mozStorageStatementScoper scoper(stmt); IDB_TRY_INSPECT(const bool& hasResults, MOZ_TO_RESULT_INVOKE(stmt, ExecuteStep)); 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("DROP TABLE database"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "CREATE TABLE database (" "name TEXT NOT NULL, " "version INTEGER NOT NULL DEFAULT 0, " "dataVersion INTEGER NOT NULL" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // The parameter names are not used, parameters are bound by index only // locally in the same function. rv = aConnection.CreateStatement( "INSERT INTO database (name, version, dataVersion) " "VALUES (:name, :version, :dataVersion)"_ns, getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } { mozStorageStatementScoper scoper(stmt); rv = stmt->BindStringByIndex(0, name); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindInt32ByIndex(1, intVersion); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindInt64ByIndex(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(); AUTO_PROFILER_LABEL("UpgradeSchemaFrom5To6", DOM); // First, drop all the indexes we're no longer going to use. nsresult rv = aConnection.ExecuteSimpleSQL("DROP INDEX key_index;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP INDEX ai_key_index;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP INDEX value_index;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP INDEX ai_value_index;"_ns); 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( "CREATE TEMPORARY TABLE temp_upgrade (" "id INTEGER PRIMARY KEY, " "object_store_id, " "key_value, " "data " ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO temp_upgrade " "SELECT id, object_store_id, key_value, data " "FROM object_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO object_data " "SELECT id, object_store_id, key_value, data " "FROM temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns); 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( "CREATE TEMPORARY TABLE temp_upgrade (" "id INTEGER PRIMARY KEY, " "object_store_id, " "data " ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO temp_upgrade " "SELECT id, object_store_id, data " "FROM ai_object_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE ai_object_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO ai_object_data " "SELECT id, object_store_id, data " "FROM temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns); 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( "CREATE TEMPORARY TABLE temp_upgrade (" "index_id, " "value, " "object_data_key, " "object_data_id " ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO temp_upgrade " "SELECT index_id, value, object_data_key, object_data_id " "FROM index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT OR IGNORE INTO index_data " "SELECT index_id, value, object_data_key, object_data_id " "FROM temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "CREATE INDEX index_data_object_data_id_index " "ON index_data (object_data_id);"_ns); 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( "CREATE TEMPORARY TABLE temp_upgrade (" "index_id, " "value, " "object_data_key, " "object_data_id " ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO temp_upgrade " "SELECT index_id, value, object_data_key, object_data_id " "FROM unique_index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE unique_index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO unique_index_data " "SELECT index_id, value, object_data_key, object_data_id " "FROM temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "CREATE INDEX unique_index_data_object_data_id_index " "ON unique_index_data (object_data_id);"_ns); 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( "CREATE TEMPORARY TABLE temp_upgrade (" "index_id, " "value, " "ai_object_data_id " ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO temp_upgrade " "SELECT index_id, value, ai_object_data_id " "FROM ai_index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE ai_index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT OR IGNORE INTO ai_index_data " "SELECT index_id, value, ai_object_data_id " "FROM temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "CREATE INDEX ai_index_data_ai_object_data_id_index " "ON ai_index_data (ai_object_data_id);"_ns); 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( "CREATE TEMPORARY TABLE temp_upgrade (" "index_id, " "value, " "ai_object_data_id " ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO temp_upgrade " "SELECT index_id, value, ai_object_data_id " "FROM ai_unique_index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE ai_unique_index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO ai_unique_index_data " "SELECT index_id, value, ai_object_data_id " "FROM temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "CREATE INDEX ai_unique_index_data_ai_object_data_id_index " "ON ai_unique_index_data (ai_object_data_id);"_ns); 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(); AUTO_PROFILER_LABEL("UpgradeSchemaFrom6To7", DOM); nsresult rv = aConnection.ExecuteSimpleSQL( "CREATE TEMPORARY TABLE temp_upgrade (" "id, " "name, " "key_path, " "auto_increment" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO temp_upgrade " "SELECT id, name, key_path, auto_increment " "FROM object_store;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_store;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "CREATE TABLE object_store (" "id INTEGER PRIMARY KEY, " "auto_increment INTEGER NOT NULL DEFAULT 0, " "name TEXT NOT NULL, " "key_path TEXT, " "UNIQUE (name)" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO object_store " "SELECT id, auto_increment, name, nullif(key_path, '') " "FROM temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns); 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(); AUTO_PROFILER_LABEL("UpgradeSchemaFrom7To8", DOM); nsresult rv = aConnection.ExecuteSimpleSQL( "CREATE TEMPORARY TABLE temp_upgrade (" "id, " "object_store_id, " "name, " "key_path, " "unique_index, " "object_store_autoincrement" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO temp_upgrade " "SELECT id, object_store_id, name, key_path, " "unique_index, object_store_autoincrement " "FROM object_store_index;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_store_index;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO object_store_index " "SELECT id, object_store_id, name, key_path, " "unique_index, 0, object_store_autoincrement " "FROM temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.SetSchemaVersion(8); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } class CompressDataBlobsFunction final : public mozIStorageFunction { public: NS_DECL_ISUPPORTS private: ~CompressDataBlobsFunction() = default; NS_IMETHOD OnFunctionCall(mozIStorageValueArray* aArguments, nsIVariant** aResult) override { MOZ_ASSERT(aArguments); MOZ_ASSERT(aResult); AUTO_PROFILER_LABEL("CompressDataBlobsFunction::OnFunctionCall", DOM); 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 compressed( static_cast(malloc(compressedLength))); if (NS_WARN_IF(!compressed)) { return NS_ERROR_OUT_OF_MEMORY; } snappy::RawCompress( reinterpret_cast(uncompressed), uncompressedLength, reinterpret_cast(compressed.get()), &compressedLength); std::pair data(compressed.release(), int(compressedLength)); nsCOMPtr result = new mozilla::storage::AdoptedBlobVariant(data); result.forget(aResult); return NS_OK; } }; nsresult UpgradeSchemaFrom8To9_0(mozIStorageConnection& aConnection) { AssertIsOnIOThread(); AUTO_PROFILER_LABEL("UpgradeSchemaFrom8To9_0", DOM); // We no longer use the dataVersion column. nsresult rv = aConnection.ExecuteSimpleSQL("UPDATE database SET dataVersion = 0;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr compressor = new CompressDataBlobsFunction(); constexpr auto compressorName = "compress"_ns; 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( "UPDATE object_data SET data = compress(data);"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "UPDATE ai_object_data SET data = compress(data);"_ns); 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(); AUTO_PROFILER_LABEL("UpgradeSchemaFrom9_0To10_0", DOM); nsresult rv = aConnection.ExecuteSimpleSQL( "ALTER TABLE object_data ADD COLUMN file_ids TEXT;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "ALTER TABLE ai_object_data ADD COLUMN file_ids TEXT;"_ns); 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(); AUTO_PROFILER_LABEL("UpgradeSchemaFrom10_0To11_0", DOM); nsresult rv = aConnection.ExecuteSimpleSQL( "CREATE TEMPORARY TABLE temp_upgrade (" "id, " "object_store_id, " "name, " "key_path, " "unique_index, " "multientry" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO temp_upgrade " "SELECT id, object_store_id, name, key_path, " "unique_index, multientry " "FROM object_store_index;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_store_index;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO object_store_index " "SELECT id, object_store_id, name, key_path, " "unique_index, multientry " "FROM temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "DROP TRIGGER object_data_insert_trigger;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO object_data (object_store_id, key_value, data, file_ids) " "SELECT object_store_id, id, data, file_ids " "FROM ai_object_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "UPDATE object_store " "SET auto_increment = (SELECT max(id) FROM ai_object_data) + 1 " "WHERE auto_increment;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE ai_unique_index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE ai_index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE ai_object_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.SetSchemaVersion(MakeSchemaVersion(11, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } class EncodeKeysFunction final : public mozIStorageFunction { public: NS_DECL_ISUPPORTS private: ~EncodeKeysFunction() = default; NS_IMETHOD OnFunctionCall(mozIStorageValueArray* aArguments, nsIVariant** aResult) override { MOZ_ASSERT(aArguments); MOZ_ASSERT(aResult); AUTO_PROFILER_LABEL("EncodeKeysFunction::OnFunctionCall", DOM); 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; } IDB_TRY_INSPECT( const auto& key, ([type, aArguments]() -> Result { switch (type) { case mozIStorageStatement::VALUE_TYPE_INTEGER: { int64_t intKey; aArguments->GetInt64(0, &intKey); Key key; key.SetFromInteger(intKey); return key; } case mozIStorageStatement::VALUE_TYPE_TEXT: { nsString stringKey; aArguments->GetString(0, stringKey); Key key; IDB_TRY(key.SetFromString(stringKey)); return key; } default: NS_WARNING("Don't call me with the wrong type of arguments!"); return Err(NS_ERROR_UNEXPECTED); } }())); const nsCString& buffer = key.GetBuffer(); std::pair data(static_cast(buffer.get()), int(buffer.Length())); nsCOMPtr result = new mozilla::storage::BlobVariant(data); result.forget(aResult); return NS_OK; } }; nsresult UpgradeSchemaFrom11_0To12_0(mozIStorageConnection& aConnection) { AssertIsOnIOThread(); AUTO_PROFILER_LABEL("UpgradeSchemaFrom11_0To12_0", DOM); constexpr auto encoderName = "encode"_ns; nsCOMPtr encoder = new EncodeKeysFunction(); nsresult rv = aConnection.CreateFunction(encoderName, 1, encoder); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "CREATE TEMPORARY TABLE temp_upgrade (" "id INTEGER PRIMARY KEY, " "object_store_id, " "key_value, " "data, " "file_ids " ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO temp_upgrade " "SELECT id, object_store_id, encode(key_value), data, file_ids " "FROM object_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO object_data " "SELECT id, object_store_id, key_value, file_ids, data " "FROM temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "CREATE TEMPORARY TABLE temp_upgrade (" "index_id, " "value, " "object_data_key, " "object_data_id " ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO temp_upgrade " "SELECT index_id, encode(value), encode(object_data_key), object_data_id " "FROM index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO index_data " "SELECT index_id, value, object_data_key, object_data_id " "FROM temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "CREATE INDEX index_data_object_data_id_index " "ON index_data (object_data_id);"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "CREATE TEMPORARY TABLE temp_upgrade (" "index_id, " "value, " "object_data_key, " "object_data_id " ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO temp_upgrade " "SELECT index_id, encode(value), encode(object_data_key), object_data_id " "FROM unique_index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE unique_index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "INSERT INTO unique_index_data " "SELECT index_id, value, object_data_key, object_data_id " "FROM temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE temp_upgrade;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "CREATE INDEX unique_index_data_object_data_id_index " "ON unique_index_data (object_data_id);"_ns); 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(); AUTO_PROFILER_LABEL("UpgradeSchemaFrom12_0To13_0", DOM); 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(); // 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() = delete; ~UpgradeSchemaFrom17_0To18_0Helper() = delete; }; class UpgradeSchemaFrom17_0To18_0Helper::InsertIndexDataValuesFunction final : public mozIStorageFunction { public: InsertIndexDataValuesFunction() = default; NS_DECL_ISUPPORTS private: ~InsertIndexDataValuesFunction() = default; NS_DECL_MOZISTORAGEFUNCTION }; NS_IMPL_ISUPPORTS( UpgradeSchemaFrom17_0To18_0Helper::InsertIndexDataValuesFunction, mozIStorageFunction); NS_IMETHODIMP UpgradeSchemaFrom17_0To18_0Helper::InsertIndexDataValuesFunction:: OnFunctionCall(mozIStorageValueArray* aValues, nsIVariant** _retval) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(!IsOnBackgroundThread()); MOZ_ASSERT(aValues); MOZ_ASSERT(_retval); #ifdef DEBUG { uint32_t argCount; MOZ_ALWAYS_SUCCEEDS(aValues->GetNumEntries(&argCount)); MOZ_ASSERT(argCount == 4); int32_t valueType; MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType)); MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_NULL || valueType == mozIStorageValueArray::VALUE_TYPE_BLOB); MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(1, &valueType)); MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER); MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(2, &valueType)); MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_INTEGER); MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(3, &valueType)); MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB); } #endif // Read out the previous value. It may be NULL, in which case we'll just end // up with an empty array. IDB_TRY_UNWRAP(auto indexValues, ReadCompressedIndexDataValues(*aValues, 0)); IndexOrObjectStoreId indexId; nsresult 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.InsertElementSorted( IndexDataValue(indexId, !!unique, value), fallible))) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_OUT_OF_MEMORY; } // Compress the array. IDB_TRY_UNWRAP((auto [indexValuesBlob, indexValuesBlobLength]), MakeCompressedIndexDataValues(indexValues)); // The compressed blob is the result of this function. nsCOMPtr result = new storage::AdoptedBlobVariant( std::pair(indexValuesBlob.release(), indexValuesBlobLength)); result.forget(_retval); return NS_OK; } class UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction final : public mozIStorageFunction { public: UpgradeKeyFunction() = default; static nsresult CopyAndUpgradeKeyBuffer(const uint8_t* aSource, const uint8_t* aSourceEnd, uint8_t* aDestination) { return CopyAndUpgradeKeyBufferInternal(aSource, aSourceEnd, aDestination, 0 /* aTagOffset */, 0 /* aRecursionDepth */); } NS_DECL_ISUPPORTS private: ~UpgradeKeyFunction() = default; static nsresult CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource, const uint8_t* aSourceEnd, uint8_t*& aDestination, uint8_t aTagOffset, uint8_t aRecursionDepth); static uint32_t AdjustedSize(uint32_t aMaxSize, const uint8_t* aSource, const uint8_t* aSourceEnd) { MOZ_ASSERT(aMaxSize); MOZ_ASSERT(aSource); MOZ_ASSERT(aSourceEnd); MOZ_ASSERT(aSource <= aSourceEnd); return std::min(aMaxSize, uint32_t(aSourceEnd - aSource)); } NS_DECL_MOZISTORAGEFUNCTION }; // static nsresult UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction:: CopyAndUpgradeKeyBufferInternal(const uint8_t*& aSource, const uint8_t* aSourceEnd, uint8_t*& aDestination, uint8_t aTagOffset, uint8_t aRecursionDepth) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(!IsOnBackgroundThread()); MOZ_ASSERT(aSource); MOZ_ASSERT(*aSource); MOZ_ASSERT(aSourceEnd); MOZ_ASSERT(aSource < aSourceEnd); MOZ_ASSERT(aDestination); MOZ_ASSERT(aTagOffset <= Key::kMaxArrayCollapse); static constexpr uint8_t kOldNumberTag = 0x1; static constexpr uint8_t kOldDateTag = 0x2; static constexpr uint8_t kOldStringTag = 0x3; static constexpr uint8_t kOldArrayTag = 0x4; static constexpr uint8_t kOldMaxType = kOldArrayTag; if (NS_WARN_IF(aRecursionDepth > Key::kMaxRecursionDepth)) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_FILE_CORRUPTED; } const uint8_t sourceTag = *aSource - (aTagOffset * kOldMaxType); MOZ_ASSERT(sourceTag); if (NS_WARN_IF(sourceTag > kOldMaxType * Key::kMaxArrayCollapse)) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_FILE_CORRUPTED; } if (sourceTag == kOldNumberTag || sourceTag == kOldDateTag) { // Write the new tag. *aDestination++ = (sourceTag == kOldNumberTag ? Key::eFloat : Key::eDate) + (aTagOffset * Key::eMaxType); aSource++; // Numbers and Dates are encoded as 64-bit integers, but trailing 0 // bytes have been removed. const uint32_t byteCount = AdjustedSize(sizeof(uint64_t), aSource, aSourceEnd); aDestination = std::copy(aSource, aSource + byteCount, aDestination); aSource += byteCount; 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); aDestination = std::copy(aSource, aSource + byteCount, aDestination); aSource += byteCount; } } return NS_OK; } if (NS_WARN_IF(sourceTag < kOldArrayTag)) { IDB_REPORT_INTERNAL_ERR(); return NS_ERROR_FILE_CORRUPTED; } aTagOffset++; if (aTagOffset == Key::kMaxArrayCollapse) { MOZ_ASSERT(sourceTag == kOldArrayTag); *aDestination++ = (aTagOffset * Key::eMaxType); aSource++; aTagOffset = 0; } while (aSource < aSourceEnd && (*aSource - (aTagOffset * kOldMaxType)) != Key::eTerminator) { nsresult rv = CopyAndUpgradeKeyBufferInternal( aSource, aSourceEnd, aDestination, aTagOffset, aRecursionDepth + 1); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } aTagOffset = 0; } if (aSource < aSourceEnd) { MOZ_ASSERT((*aSource - (aTagOffset * kOldMaxType)) == Key::eTerminator); *aDestination++ = Key::eTerminator + (aTagOffset * Key::eMaxType); aSource++; } return NS_OK; } NS_IMPL_ISUPPORTS(UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction, mozIStorageFunction); NS_IMETHODIMP UpgradeSchemaFrom17_0To18_0Helper::UpgradeKeyFunction::OnFunctionCall( mozIStorageValueArray* aValues, nsIVariant** _retval) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(!IsOnBackgroundThread()); MOZ_ASSERT(aValues); MOZ_ASSERT(_retval); #ifdef DEBUG { uint32_t argCount; MOZ_ALWAYS_SUCCEEDS(aValues->GetNumEntries(&argCount)); MOZ_ASSERT(argCount == 1); int32_t valueType; MOZ_ALWAYS_SUCCEEDS(aValues->GetTypeOfIndex(0, &valueType)); MOZ_ASSERT(valueType == mozIStorageValueArray::VALUE_TYPE_BLOB); } #endif // Dig the old key out of the values. const uint8_t* blobData; uint32_t blobDataLength; nsresult rv = aValues->GetSharedBlob(0, &blobDataLength, &blobData); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Upgrading the key doesn't change the amount of space needed to hold it. UniqueFreePtr upgradedBlobData( static_cast(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 data(upgradedBlobData.release(), int(blobDataLength)); nsCOMPtr result = new mozilla::storage::AdoptedBlobVariant(data); result.forget(_retval); return NS_OK; } // static nsresult UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade( mozIStorageConnection& aConnection, const nsACString& aOrigin) { MOZ_ASSERT(!aOrigin.IsEmpty()); // Register the |upgrade_key| function. RefPtr updateFunction = new UpgradeKeyFunction(); constexpr auto upgradeKeyFunctionName = "upgrade_key"_ns; nsresult rv = aConnection.CreateFunction(upgradeKeyFunctionName, 1, updateFunction); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Register the |insert_idv| function. RefPtr insertIDVFunction = new InsertIndexDataValuesFunction(); constexpr auto insertIDVFunctionName = "insert_idv"_ns; rv = aConnection.CreateFunction(insertIDVFunctionName, 4, insertIDVFunction); if (NS_WARN_IF(NS_FAILED(rv))) { MOZ_ALWAYS_SUCCEEDS(aConnection.RemoveFunction(upgradeKeyFunctionName)); return rv; } rv = DoUpgradeInternal(aConnection, aOrigin); MOZ_ALWAYS_SUCCEEDS(aConnection.RemoveFunction(upgradeKeyFunctionName)); MOZ_ALWAYS_SUCCEEDS(aConnection.RemoveFunction(insertIDVFunctionName)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } // static nsresult UpgradeSchemaFrom17_0To18_0Helper::DoUpgradeInternal( mozIStorageConnection& aConnection, const nsACString& aOrigin) { MOZ_ASSERT(!aOrigin.IsEmpty()); nsresult rv = aConnection.ExecuteSimpleSQL( // 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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // 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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Create the new tables and triggers first. rv = aConnection.ExecuteSimpleSQL( // 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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // 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" ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // 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) " ");"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // 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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // 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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // 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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // 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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // 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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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;"_ns); 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( // 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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // The trigger is no longer needed. "DROP TRIGGER unique_index_data_upgrade_insert_trigger;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // The old table is no longer needed. "DROP TABLE unique_index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // Rename the table. "ALTER TABLE unique_index_data_upgrade " "RENAME TO unique_index_data;"_ns); 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( // 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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // The trigger is no longer needed. "DROP TRIGGER index_data_upgrade_insert_trigger;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // The old table is no longer needed. "DROP TABLE index_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // Rename the table. "ALTER TABLE index_data_upgrade " "RENAME TO index_data;"_ns); 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( // 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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // The temporary table is no longer needed. "DROP TABLE temp_index_data_values;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // The old table is no longer needed. "DROP TABLE object_data;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( // Rename the table. "ALTER TABLE object_data_upgrade " "RENAME TO object_data;"_ns); 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( "INSERT INTO object_store_index_upgrade " "SELECT * " "FROM object_store_index;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_store_index;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "ALTER TABLE object_store_index_upgrade " "RENAME TO object_store_index;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Update the |object_store| table to remove the UNIQUE constraint. rv = aConnection.ExecuteSimpleSQL( "INSERT INTO object_store_upgrade " "SELECT * " "FROM object_store;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL("DROP TABLE object_store;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "ALTER TABLE object_store_upgrade " "RENAME TO object_store;"_ns); 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 stmt; // The parameter names are not used, parameters are bound by index only // locally in the same function. rv = aConnection.CreateStatement( "INSERT INTO database_upgrade " "SELECT name, :origin, version, 0, 0, 0 " "FROM database;"_ns, getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindUTF8StringByIndex(0, 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("DROP TABLE database;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "ALTER TABLE database_upgrade " "RENAME TO database;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } #ifdef DEBUG { // Make sure there's only one entry in the |database| table. IDB_TRY_INSPECT(const auto& stmt, quota::CreateAndExecuteSingleStepStatement( aConnection, "SELECT COUNT(*) FROM database;"_ns), QM_ASSERT_UNREACHABLE); int64_t count; MOZ_ASSERT(NS_SUCCEEDED(stmt->GetInt64(0, &count))); MOZ_ASSERT(count == 1); } #endif // Recreate file table triggers. rv = aConnection.ExecuteSimpleSQL( "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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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;"_ns); 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 "PRAGMA auto_vacuum = FULL;"_ns #else "PRAGMA auto_vacuum = INCREMENTAL;"_ns #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(!aOrigin.IsEmpty()); AUTO_PROFILER_LABEL("UpgradeSchemaFrom17_0To18_0", DOM); return UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(aConnection, aOrigin); } nsresult UpgradeSchemaFrom18_0To19_0(mozIStorageConnection& aConnection) { AssertIsOnIOThread(); nsresult rv; AUTO_PROFILER_LABEL("UpgradeSchemaFrom18_0To19_0", DOM); rv = aConnection.ExecuteSimpleSQL( "ALTER TABLE object_store_index " "ADD COLUMN locale TEXT;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "ALTER TABLE object_store_index " "ADD COLUMN is_auto_locale BOOLEAN;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "ALTER TABLE index_data " "ADD COLUMN value_locale BLOB;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "ALTER TABLE unique_index_data " "ADD COLUMN value_locale BLOB;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "CREATE INDEX index_data_value_locale_index " "ON index_data (index_id, value_locale, object_data_key, value) " "WHERE value_locale IS NOT NULL;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.SetSchemaVersion(MakeSchemaVersion(19, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } class UpgradeFileIdsFunction final : public mozIStorageFunction { SafeRefPtr mFileManager; public: UpgradeFileIdsFunction() { AssertIsOnIOThread(); } nsresult Init(nsIFile* aFMDirectory, mozIStorageConnection& aConnection); NS_DECL_ISUPPORTS private: ~UpgradeFileIdsFunction() { AssertIsOnIOThread(); if (mFileManager) { mFileManager->Invalidate(); } } NS_IMETHOD OnFunctionCall(mozIStorageValueArray* aArguments, nsIVariant** aResult) override; }; nsresult UpgradeSchemaFrom19_0To20_0(nsIFile* aFMDirectory, mozIStorageConnection& aConnection) { AssertIsOnIOThread(); AUTO_PROFILER_LABEL("UpgradeSchemaFrom19_0To20_0", DOM); nsCOMPtr stmt; nsresult rv = aConnection.CreateStatement( "SELECT count(*) " "FROM object_data " "WHERE file_ids IS NOT NULL"_ns, getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } int64_t count; { mozStorageStatementScoper scoper(stmt); IDB_TRY_INSPECT(const bool& hasResult, MOZ_TO_RESULT_INVOKE(stmt, ExecuteStep)); 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 function = new UpgradeFileIdsFunction(); rv = function->Init(aFMDirectory, aConnection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } constexpr auto functionName = "upgrade"_ns; rv = aConnection.CreateFunction(functionName, 2, function); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Disable update trigger. rv = aConnection.ExecuteSimpleSQL( "DROP TRIGGER object_data_update_trigger;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "UPDATE object_data " "SET file_ids = upgrade(file_ids, data) " "WHERE file_ids IS NOT NULL;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Enable update trigger. rv = aConnection.ExecuteSimpleSQL( "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;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.RemoveFunction(functionName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.SetSchemaVersion(MakeSchemaVersion(20, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } class UpgradeIndexDataValuesFunction final : public mozIStorageFunction { public: UpgradeIndexDataValuesFunction() { AssertIsOnIOThread(); } NS_DECL_ISUPPORTS private: ~UpgradeIndexDataValuesFunction() { AssertIsOnIOThread(); } using IndexDataValuesArray = IndexDataValuesAutoArray; Result ReadOldCompressedIDVFromBlob( Span aBlobData); NS_IMETHOD OnFunctionCall(mozIStorageValueArray* aArguments, nsIVariant** aResult) override; }; NS_IMPL_ISUPPORTS(UpgradeIndexDataValuesFunction, mozIStorageFunction) Result UpgradeIndexDataValuesFunction::ReadOldCompressedIDVFromBlob( const Span aBlobData) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(!IsOnBackgroundThread()); IndexOrObjectStoreId indexId; bool unique; bool nextIndexIdAlreadyRead = false; IndexDataValuesArray result; for (auto remainder = aBlobData; !remainder.IsEmpty();) { if (!nextIndexIdAlreadyRead) { IDB_TRY_UNWRAP((std::tie(indexId, unique, remainder)), ReadCompressedIndexId(remainder)); } nextIndexIdAlreadyRead = false; if (NS_WARN_IF(remainder.IsEmpty())) { IDB_REPORT_INTERNAL_ERR(); return Err(NS_ERROR_FILE_CORRUPTED); } // Read key buffer length. IDB_TRY_INSPECT( (const auto& [keyBufferLength, remainderAfterKeyBufferLength]), ReadCompressedNumber(remainder)); if (NS_WARN_IF(remainderAfterKeyBufferLength.IsEmpty()) || NS_WARN_IF(keyBufferLength > uint64_t(UINT32_MAX)) || NS_WARN_IF(keyBufferLength > remainderAfterKeyBufferLength.Length())) { IDB_REPORT_INTERNAL_ERR(); return Err(NS_ERROR_FILE_CORRUPTED); } const auto [keyBuffer, remainderAfterKeyBuffer] = remainderAfterKeyBufferLength.SplitAt(keyBufferLength); if (NS_WARN_IF(!result.EmplaceBack(fallible, indexId, unique, Key{nsCString{AsChars(keyBuffer)}}))) { IDB_REPORT_INTERNAL_ERR(); return Err(NS_ERROR_OUT_OF_MEMORY); } remainder = remainderAfterKeyBuffer; if (!remainder.IsEmpty()) { // Read either a sort key buffer length or an index id. IDB_TRY_INSPECT((const auto& [maybeIndexId, remainderAfterIndexId]), ReadCompressedNumber(remainder)); // 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. // XXX This duplicates logic from ReadCompressedIndexId. if (maybeIndexId != 0) { unique = maybeIndexId % 2 == 1; indexId = maybeIndexId / 2; nextIndexIdAlreadyRead = true; } remainder = remainderAfterIndexId; } } result.Sort(); return result; } NS_IMETHODIMP UpgradeIndexDataValuesFunction::OnFunctionCall( mozIStorageValueArray* aArguments, nsIVariant** aResult) { MOZ_ASSERT(aArguments); MOZ_ASSERT(aResult); AUTO_PROFILER_LABEL("UpgradeIndexDataValuesFunction::OnFunctionCall", DOM); 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; } IDB_TRY_INSPECT(const auto& oldIdv, ReadOldCompressedIDVFromBlob(Span(oldBlob, oldBlobLength))); IDB_TRY_UNWRAP((auto [newIdv, newIdvLength]), MakeCompressedIndexDataValues(oldIdv)); nsCOMPtr result = new storage::AdoptedBlobVariant( std::pair(newIdv.release(), newIdvLength)); 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(); AUTO_PROFILER_LABEL("UpgradeSchemaFrom20_0To21_0", DOM); RefPtr function = new UpgradeIndexDataValuesFunction(); constexpr auto functionName = "upgrade_idv"_ns; nsresult rv = aConnection.CreateFunction(functionName, 1, function); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "UPDATE object_data " "SET index_data_values = upgrade_idv(index_data_values) " "WHERE index_data_values IS NOT NULL;"_ns); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.RemoveFunction(functionName); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.SetSchemaVersion(MakeSchemaVersion(21, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult UpgradeSchemaFrom21_0To22_0(mozIStorageConnection& aConnection) { // The only change between 21 and 22 was a different structured clone format, // but it's backwards-compatible. nsresult rv = aConnection.SetSchemaVersion(MakeSchemaVersion(22, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult UpgradeSchemaFrom22_0To23_0(mozIStorageConnection& aConnection, const nsACString& aOrigin) { AssertIsOnIOThread(); MOZ_ASSERT(!aOrigin.IsEmpty()); AUTO_PROFILER_LABEL("UpgradeSchemaFrom22_0To23_0", DOM); nsCOMPtr stmt; // The parameter names are not used, parameters are bound by index only // locally in the same function. nsresult rv = aConnection.CreateStatement( "UPDATE database SET origin = :origin;"_ns, getter_AddRefs(stmt)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->BindUTF8StringByIndex(0, aOrigin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = stmt->Execute(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.SetSchemaVersion(MakeSchemaVersion(23, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult UpgradeSchemaFrom23_0To24_0(mozIStorageConnection& aConnection) { // The only change between 23 and 24 was a different structured clone format, // but it's backwards-compatible. nsresult rv = aConnection.SetSchemaVersion(MakeSchemaVersion(24, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } nsresult UpgradeSchemaFrom24_0To25_0(mozIStorageConnection& aConnection) { // The changes between 24 and 25 were an upgraded snappy library, a different // structured clone format and a different file_ds format. But everything is // backwards-compatible. nsresult rv = aConnection.SetSchemaVersion(MakeSchemaVersion(25, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } class StripObsoleteOriginAttributesFunction final : public mozIStorageFunction { public: NS_DECL_ISUPPORTS private: ~StripObsoleteOriginAttributesFunction() = default; NS_IMETHOD OnFunctionCall(mozIStorageValueArray* aArguments, nsIVariant** aResult) override { MOZ_ASSERT(aArguments); MOZ_ASSERT(aResult); AUTO_PROFILER_LABEL("StripObsoleteOriginAttributesFunction::OnFunctionCall", DOM); #ifdef DEBUG { uint32_t argCount; MOZ_ALWAYS_SUCCEEDS(aArguments->GetNumEntries(&argCount)); MOZ_ASSERT(argCount == 1); int32_t type; MOZ_ALWAYS_SUCCEEDS(aArguments->GetTypeOfIndex(0, &type)); MOZ_ASSERT(type == mozIStorageValueArray::VALUE_TYPE_TEXT); } #endif nsCString origin; nsresult rv = aArguments->GetUTF8String(0, origin); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // Deserialize and re-serialize to automatically drop any obsolete origin // attributes. OriginAttributes oa; nsCString originNoSuffix; bool ok = oa.PopulateFromOrigin(origin, originNoSuffix); if (NS_WARN_IF(!ok)) { return NS_ERROR_FAILURE; } nsCString suffix; oa.CreateSuffix(suffix); nsCOMPtr result = new mozilla::storage::UTF8TextVariant(originNoSuffix + suffix); result.forget(aResult); return NS_OK; } }; nsresult UpgradeSchemaFrom25_0To26_0(mozIStorageConnection& aConnection) { AssertIsOnIOThread(); AUTO_PROFILER_LABEL("UpgradeSchemaFrom25_0To26_0", DOM); constexpr auto functionName = "strip_obsolete_attributes"_ns; nsCOMPtr stripObsoleteAttributes = new StripObsoleteOriginAttributesFunction(); nsresult rv = aConnection.CreateFunction(functionName, /* aNumArguments */ 1, stripObsoleteAttributes); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } rv = aConnection.ExecuteSimpleSQL( "UPDATE DATABASE " "SET origin = strip_obsolete_attributes(origin) " "WHERE origin LIKE '%^%';"_ns); 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(26, 0)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return NS_OK; } NS_IMPL_ISUPPORTS(CompressDataBlobsFunction, mozIStorageFunction) NS_IMPL_ISUPPORTS(EncodeKeysFunction, mozIStorageFunction) NS_IMPL_ISUPPORTS(StripObsoleteOriginAttributesFunction, mozIStorageFunction); class DeserializeUpgradeValueHelper final : public Runnable { public: explicit DeserializeUpgradeValueHelper( StructuredCloneReadInfoParent& aCloneReadInfo) : Runnable("DeserializeUpgradeValueHelper"), mMonitor("DeserializeUpgradeValueHelper::mMonitor"), mCloneReadInfo(aCloneReadInfo), mStatus(NS_ERROR_FAILURE) {} nsresult DispatchAndWait(nsAString& aFileIds) { // We don't need to go to the main-thread and use the sandbox. if (!mCloneReadInfo.Data().Size()) { PopulateFileIds(aFileIds); return NS_OK; } // The operation will continue on the main-thread. MOZ_ASSERT(!(mCloneReadInfo.Data().Size() % sizeof(uint64_t))); MonitorAutoLock lock(mMonitor); RefPtr self = this; const nsresult rv = SchedulerGroup::Dispatch(TaskCategory::Other, self.forget()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } lock.Wait(); if (NS_FAILED(mStatus)) { return mStatus; } PopulateFileIds(aFileIds); return NS_OK; } NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); AutoJSAPI jsapi; jsapi.Init(); JSContext* cx = jsapi.cx(); JS::Rooted global(cx, GetSandbox(cx)); if (NS_WARN_IF(!global)) { OperationCompleted(NS_ERROR_FAILURE); return NS_OK; } const JSAutoRealm ar(cx, global); JS::Rooted value(cx); const nsresult rv = DeserializeUpgradeValue(cx, &value); if (NS_WARN_IF(NS_FAILED(rv))) { OperationCompleted(rv); return NS_OK; } OperationCompleted(NS_OK); return NS_OK; } private: nsresult DeserializeUpgradeValue(JSContext* aCx, JS::MutableHandle aValue) { static const JSStructuredCloneCallbacks callbacks = { StructuredCloneReadCallback, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; if (!JS_ReadStructuredClone( aCx, mCloneReadInfo.Data(), JS_STRUCTURED_CLONE_VERSION, JS::StructuredCloneScope::DifferentProcessForIndexedDB, aValue, JS::CloneDataPolicy(), &callbacks, &mCloneReadInfo)) { return NS_ERROR_DOM_DATA_CLONE_ERR; } return NS_OK; } void PopulateFileIds(nsAString& aFileIds) { for (uint32_t count = mCloneReadInfo.Files().Length(), index = 0; index < count; index++) { const StructuredCloneFileParent& file = mCloneReadInfo.Files()[index]; const int64_t id = file.FileInfo().Id(); if (index) { aFileIds.Append(' '); } aFileIds.AppendInt(file.Type() == StructuredCloneFileBase::eBlob ? id : -id); } } void OperationCompleted(nsresult aStatus) { mStatus = aStatus; MonitorAutoLock lock(mMonitor); lock.Notify(); } Monitor mMonitor; StructuredCloneReadInfoParent& mCloneReadInfo; nsresult mStatus; }; nsresult DeserializeUpgradeValueToFileIds( StructuredCloneReadInfoParent& aCloneReadInfo, nsAString& aFileIds) { MOZ_ASSERT(!NS_IsMainThread()); const RefPtr helper = new DeserializeUpgradeValueHelper(aCloneReadInfo); return helper->DispatchAndWait(aFileIds); } nsresult UpgradeFileIdsFunction::Init(nsIFile* aFMDirectory, mozIStorageConnection& aConnection) { // This file manager doesn't need real origin info, etc. The only purpose is // to store file ids without adding more complexity or code duplication. auto fileManager = MakeSafeRefPtr( PERSISTENCE_TYPE_INVALID, quota::GroupAndOrigin{}, u""_ns, false); nsresult rv = fileManager->Init(aFMDirectory, aConnection); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mFileManager = std::move(fileManager); return NS_OK; } NS_IMPL_ISUPPORTS(UpgradeFileIdsFunction, mozIStorageFunction) NS_IMETHODIMP UpgradeFileIdsFunction::OnFunctionCall(mozIStorageValueArray* aArguments, nsIVariant** aResult) { MOZ_ASSERT(aArguments); MOZ_ASSERT(aResult); MOZ_ASSERT(mFileManager); AUTO_PROFILER_LABEL("UpgradeFileIdsFunction::OnFunctionCall", DOM); uint32_t argc; nsresult rv = aArguments->GetNumEntries(&argc); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } if (argc != 2) { NS_WARNING("Don't call me with the wrong number of arguments!"); return NS_ERROR_UNEXPECTED; } IDB_TRY_UNWRAP(auto cloneInfo, GetStructuredCloneReadInfoFromValueArray( aArguments, 1, 0, *mFileManager, Nothing{})); nsAutoString fileIds; // XXX does this really need non-const cloneInfo? rv = DeserializeUpgradeValueToFileIds(cloneInfo, fileIds); if (NS_WARN_IF(NS_FAILED(rv))) { return NS_ERROR_DOM_DATA_CLONE_ERR; } nsCOMPtr result = new mozilla::storage::TextVariant(fileIds); result.forget(aResult); return NS_OK; } } // namespace Result MaybeUpgradeSchema(mozIStorageConnection& aConnection, const int32_t aSchemaVersion, nsIFile& aFMDirectory, const nsACString& aOrigin) { bool vacuumNeeded = false; int32_t schemaVersion = aSchemaVersion; // This logic needs to change next time we change the schema! static_assert(kSQLiteSchemaVersion == int32_t((26 << 4) + 0), "Upgrade function needed due to schema version increase."); while (schemaVersion != kSQLiteSchemaVersion) { switch (schemaVersion) { case 4: IDB_TRY(UpgradeSchemaFrom4To5(aConnection)); break; case 5: IDB_TRY(UpgradeSchemaFrom5To6(aConnection)); break; case 6: IDB_TRY(UpgradeSchemaFrom6To7(aConnection)); break; case 7: IDB_TRY(UpgradeSchemaFrom7To8(aConnection)); break; case 8: IDB_TRY(UpgradeSchemaFrom8To9_0(aConnection)); vacuumNeeded = true; break; case MakeSchemaVersion(9, 0): IDB_TRY(UpgradeSchemaFrom9_0To10_0(aConnection)); break; case MakeSchemaVersion(10, 0): IDB_TRY(UpgradeSchemaFrom10_0To11_0(aConnection)); break; case MakeSchemaVersion(11, 0): IDB_TRY(UpgradeSchemaFrom11_0To12_0(aConnection)); break; case MakeSchemaVersion(12, 0): IDB_TRY(UpgradeSchemaFrom12_0To13_0(aConnection, &vacuumNeeded)); break; case MakeSchemaVersion(13, 0): IDB_TRY(UpgradeSchemaFrom13_0To14_0(aConnection)); break; case MakeSchemaVersion(14, 0): IDB_TRY(UpgradeSchemaFrom14_0To15_0(aConnection)); break; case MakeSchemaVersion(15, 0): IDB_TRY(UpgradeSchemaFrom15_0To16_0(aConnection)); break; case MakeSchemaVersion(16, 0): IDB_TRY(UpgradeSchemaFrom16_0To17_0(aConnection)); break; case MakeSchemaVersion(17, 0): IDB_TRY(UpgradeSchemaFrom17_0To18_0(aConnection, aOrigin)); vacuumNeeded = true; break; case MakeSchemaVersion(18, 0): IDB_TRY(UpgradeSchemaFrom18_0To19_0(aConnection)); break; case MakeSchemaVersion(19, 0): IDB_TRY(UpgradeSchemaFrom19_0To20_0(&aFMDirectory, aConnection)); break; case MakeSchemaVersion(20, 0): IDB_TRY(UpgradeSchemaFrom20_0To21_0(aConnection)); break; case MakeSchemaVersion(21, 0): IDB_TRY(UpgradeSchemaFrom21_0To22_0(aConnection)); break; case MakeSchemaVersion(22, 0): IDB_TRY(UpgradeSchemaFrom22_0To23_0(aConnection, aOrigin)); break; case MakeSchemaVersion(23, 0): IDB_TRY(UpgradeSchemaFrom23_0To24_0(aConnection)); break; case MakeSchemaVersion(24, 0): IDB_TRY(UpgradeSchemaFrom24_0To25_0(aConnection)); break; case MakeSchemaVersion(25, 0): IDB_TRY(UpgradeSchemaFrom25_0To26_0(aConnection)); break; default: IDB_FAIL(Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), []() { IDB_WARNING( "Unable to open IndexedDB database, no upgrade path is " "available!"); }); } IDB_TRY_UNWRAP(schemaVersion, MOZ_TO_RESULT_INVOKE(aConnection, GetSchemaVersion)); } MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion); return vacuumNeeded; } } // namespace mozilla::dom::indexedDB #undef IDB_MOBILE