forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			3021 lines
		
	
	
	
		
			84 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			3021 lines
		
	
	
	
		
			84 KiB
		
	
	
	
		
			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 "SchemaUpgrades.h"
 | |
| 
 | |
| // local includes
 | |
| #include "ActorsParentCommon.h"
 | |
| #include "DatabaseFileInfo.h"
 | |
| #include "DatabaseFileManager.h"
 | |
| #include "DBSchema.h"
 | |
| #include "IndexedDatabase.h"
 | |
| #include "IndexedDatabaseInlines.h"
 | |
| #include "IndexedDBCommon.h"
 | |
| #include "ReportInternalError.h"
 | |
| 
 | |
| // global includes
 | |
| #include <stdlib.h>
 | |
| #include <algorithm>
 | |
| #include <tuple>
 | |
| #include <type_traits>
 | |
| #include <utility>
 | |
| #include "ErrorList.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/dom/quota/ResultExtensions.h"
 | |
| #include "mozilla/fallible.h"
 | |
| #include "mozilla/ipc/BackgroundParent.h"
 | |
| #include "mozilla/mozalloc.h"
 | |
| #include "mozilla/ProfilerLabels.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<mozIStorageStatement> 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);
 | |
| 
 | |
|     QM_TRY_INSPECT(const bool& hasResults,
 | |
|                    MOZ_TO_RESULT_INVOKE_MEMBER(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<uint8_t> compressed(
 | |
|         static_cast<uint8_t*>(malloc(compressedLength)));
 | |
|     if (NS_WARN_IF(!compressed)) {
 | |
|       return NS_ERROR_OUT_OF_MEMORY;
 | |
|     }
 | |
| 
 | |
|     snappy::RawCompress(
 | |
|         reinterpret_cast<const char*>(uncompressed), uncompressedLength,
 | |
|         reinterpret_cast<char*>(compressed.get()), &compressedLength);
 | |
| 
 | |
|     std::pair<uint8_t*, int> data(compressed.release(), int(compressedLength));
 | |
| 
 | |
|     nsCOMPtr<nsIVariant> result =
 | |
|         new mozilla::storage::AdoptedBlobVariant(data);
 | |
| 
 | |
|     result.forget(aResult);
 | |
|     return NS_OK;
 | |
|   }
 | |
| };
 | |
| 
 | |
| nsresult UpgradeSchemaFrom8To9_0(mozIStorageConnection& aConnection) {
 | |
|   AssertIsOnIOThread();
 | |
| 
 | |
|   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<mozIStorageFunction> 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;
 | |
|     }
 | |
| 
 | |
|     QM_TRY_INSPECT(
 | |
|         const auto& key, ([type, aArguments]() -> Result<Key, nsresult> {
 | |
|           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;
 | |
|               QM_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<const void*, int> data(static_cast<const void*>(buffer.get()),
 | |
|                                      int(buffer.Length()));
 | |
| 
 | |
|     nsCOMPtr<nsIVariant> result = new mozilla::storage::BlobVariant(data);
 | |
| 
 | |
|     result.forget(aResult);
 | |
|     return NS_OK;
 | |
|   }
 | |
| };
 | |
| 
 | |
| nsresult UpgradeSchemaFrom11_0To12_0(mozIStorageConnection& aConnection) {
 | |
|   AssertIsOnIOThread();
 | |
| 
 | |
|   AUTO_PROFILER_LABEL("UpgradeSchemaFrom11_0To12_0", DOM);
 | |
| 
 | |
|   constexpr auto encoderName = "encode"_ns;
 | |
| 
 | |
|   nsCOMPtr<mozIStorageFunction> 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.
 | |
|   QM_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.
 | |
|   QM_TRY_UNWRAP((auto [indexValuesBlob, indexValuesBlobLength]),
 | |
|                 MakeCompressedIndexDataValues(indexValues));
 | |
| 
 | |
|   // The compressed blob is the result of this function.
 | |
|   nsCOMPtr<nsIVariant> 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<uint8_t> upgradedBlobData(
 | |
|       static_cast<uint8_t*>(malloc(blobDataLength)));
 | |
|   if (NS_WARN_IF(!upgradedBlobData)) {
 | |
|     IDB_REPORT_INTERNAL_ERR();
 | |
|     return NS_ERROR_OUT_OF_MEMORY;
 | |
|   }
 | |
| 
 | |
|   rv = CopyAndUpgradeKeyBuffer(blobData, blobData + blobDataLength,
 | |
|                                upgradedBlobData.get());
 | |
|   if (NS_WARN_IF(NS_FAILED(rv))) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   // The upgraded key is the result of this function.
 | |
|   std::pair<uint8_t*, int> data(upgradedBlobData.release(),
 | |
|                                 int(blobDataLength));
 | |
| 
 | |
|   nsCOMPtr<nsIVariant> result = new mozilla::storage::AdoptedBlobVariant(data);
 | |
| 
 | |
|   result.forget(_retval);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // static
 | |
| nsresult UpgradeSchemaFrom17_0To18_0Helper::DoUpgrade(
 | |
|     mozIStorageConnection& aConnection, const nsACString& aOrigin) {
 | |
|   MOZ_ASSERT(!aOrigin.IsEmpty());
 | |
| 
 | |
|   // Register the |upgrade_key| function.
 | |
|   RefPtr<UpgradeKeyFunction> 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<InsertIndexDataValuesFunction> 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<mozIStorageStatement> 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.
 | |
|     QM_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<DatabaseFileManager> 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<mozIStorageStatement> 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);
 | |
| 
 | |
|     QM_TRY_INSPECT(const bool& hasResult,
 | |
|                    MOZ_TO_RESULT_INVOKE_MEMBER(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<UpgradeFileIdsFunction> 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<IndexDataValuesArray, nsresult> ReadOldCompressedIDVFromBlob(
 | |
|       Span<const uint8_t> aBlobData);
 | |
| 
 | |
|   NS_IMETHOD
 | |
|   OnFunctionCall(mozIStorageValueArray* aArguments,
 | |
|                  nsIVariant** aResult) override;
 | |
| };
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(UpgradeIndexDataValuesFunction, mozIStorageFunction)
 | |
| 
 | |
| Result<UpgradeIndexDataValuesFunction::IndexDataValuesArray, nsresult>
 | |
| UpgradeIndexDataValuesFunction::ReadOldCompressedIDVFromBlob(
 | |
|     const Span<const uint8_t> 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) {
 | |
|       QM_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.
 | |
|     QM_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.
 | |
|       QM_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;
 | |
|   }
 | |
| 
 | |
|   QM_TRY_INSPECT(const auto& oldIdv,
 | |
|                  ReadOldCompressedIDVFromBlob(Span(oldBlob, oldBlobLength)));
 | |
| 
 | |
|   QM_TRY_UNWRAP((auto [newIdv, newIdvLength]),
 | |
|                 MakeCompressedIndexDataValues(oldIdv));
 | |
| 
 | |
|   nsCOMPtr<nsIVariant> 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<UpgradeIndexDataValuesFunction> 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<mozIStorageStatement> 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<nsIVariant> 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<mozIStorageFunction> 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<Runnable> 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<JSObject*> global(cx, GetSandbox(cx));
 | |
|     if (NS_WARN_IF(!global)) {
 | |
|       OperationCompleted(NS_ERROR_FAILURE);
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     const JSAutoRealm ar(cx, global);
 | |
| 
 | |
|     JS::Rooted<JS::Value> 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<JS::Value> aValue) {
 | |
|     static const JSStructuredCloneCallbacks callbacks = {
 | |
|         StructuredCloneReadCallback<StructuredCloneReadInfoParent>,
 | |
|         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 MOZ_UNANNOTATED;
 | |
|   StructuredCloneReadInfoParent& mCloneReadInfo;
 | |
|   nsresult mStatus;
 | |
| };
 | |
| 
 | |
| nsresult DeserializeUpgradeValueToFileIds(
 | |
|     StructuredCloneReadInfoParent& aCloneReadInfo, nsAString& aFileIds) {
 | |
|   MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|   const RefPtr<DeserializeUpgradeValueHelper> helper =
 | |
|       new DeserializeUpgradeValueHelper(aCloneReadInfo);
 | |
|   return helper->DispatchAndWait(aFileIds);
 | |
| }
 | |
| 
 | |
| nsresult UpgradeFileIdsFunction::Init(nsIFile* aFMDirectory,
 | |
|                                       mozIStorageConnection& aConnection) {
 | |
|   // This DatabaseFileManager 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<DatabaseFileManager>(
 | |
|       PERSISTENCE_TYPE_INVALID, quota::OriginMetadata{}, 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;
 | |
|   }
 | |
| 
 | |
|   QM_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<nsIVariant> result = new mozilla::storage::TextVariant(fileIds);
 | |
| 
 | |
|   result.forget(aResult);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| }  // namespace
 | |
| 
 | |
| Result<bool, nsresult> 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:
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom4To5(aConnection)));
 | |
|         break;
 | |
|       case 5:
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom5To6(aConnection)));
 | |
|         break;
 | |
|       case 6:
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom6To7(aConnection)));
 | |
|         break;
 | |
|       case 7:
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom7To8(aConnection)));
 | |
|         break;
 | |
|       case 8:
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom8To9_0(aConnection)));
 | |
|         vacuumNeeded = true;
 | |
|         break;
 | |
|       case MakeSchemaVersion(9, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom9_0To10_0(aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(10, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom10_0To11_0(aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(11, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom11_0To12_0(aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(12, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(
 | |
|             UpgradeSchemaFrom12_0To13_0(aConnection, &vacuumNeeded)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(13, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom13_0To14_0(aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(14, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom14_0To15_0(aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(15, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom15_0To16_0(aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(16, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom16_0To17_0(aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(17, 0):
 | |
|         QM_TRY(
 | |
|             MOZ_TO_RESULT(UpgradeSchemaFrom17_0To18_0(aConnection, aOrigin)));
 | |
|         vacuumNeeded = true;
 | |
|         break;
 | |
|       case MakeSchemaVersion(18, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom18_0To19_0(aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(19, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(
 | |
|             UpgradeSchemaFrom19_0To20_0(&aFMDirectory, aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(20, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom20_0To21_0(aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(21, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom21_0To22_0(aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(22, 0):
 | |
|         QM_TRY(
 | |
|             MOZ_TO_RESULT(UpgradeSchemaFrom22_0To23_0(aConnection, aOrigin)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(23, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom23_0To24_0(aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(24, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom24_0To25_0(aConnection)));
 | |
|         break;
 | |
|       case MakeSchemaVersion(25, 0):
 | |
|         QM_TRY(MOZ_TO_RESULT(UpgradeSchemaFrom25_0To26_0(aConnection)));
 | |
|         break;
 | |
|       default:
 | |
|         QM_FAIL(Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR), []() {
 | |
|           IDB_WARNING(
 | |
|               "Unable to open IndexedDB database, no upgrade path is "
 | |
|               "available!");
 | |
|         });
 | |
|     }
 | |
| 
 | |
|     QM_TRY_UNWRAP(schemaVersion,
 | |
|                   MOZ_TO_RESULT_INVOKE_MEMBER(aConnection, GetSchemaVersion));
 | |
|   }
 | |
| 
 | |
|   MOZ_ASSERT(schemaVersion == kSQLiteSchemaVersion);
 | |
| 
 | |
|   return vacuumNeeded;
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla::dom::indexedDB
 | |
| 
 | |
| #undef IDB_MOBILE
 | 
