michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "OpenDatabaseHelper.h" michael@0: michael@0: #include "nsIBFCacheEntry.h" michael@0: #include "nsIFile.h" michael@0: michael@0: #include michael@0: #include "mozilla/dom/quota/AcquireListener.h" michael@0: #include "mozilla/dom/quota/OriginOrPatternString.h" michael@0: #include "mozilla/dom/quota/QuotaManager.h" michael@0: #include "mozilla/storage.h" michael@0: #include "nsEscape.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsThreadUtils.h" michael@0: #include "snappy/snappy.h" michael@0: michael@0: #include "Client.h" michael@0: #include "IDBEvents.h" michael@0: #include "IDBFactory.h" michael@0: #include "IndexedDatabaseManager.h" michael@0: #include "ProfilerHelpers.h" michael@0: #include "ReportInternalError.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: USING_INDEXEDDB_NAMESPACE michael@0: USING_QUOTA_NAMESPACE michael@0: michael@0: namespace { michael@0: michael@0: // If JS_STRUCTURED_CLONE_VERSION changes then we need to update our major michael@0: // schema version. michael@0: static_assert(JS_STRUCTURED_CLONE_VERSION == 2, michael@0: "Need to update the major schema version."); michael@0: michael@0: // Major schema version. Bump for almost everything. michael@0: const uint32_t kMajorSchemaVersion = 14; michael@0: michael@0: // Minor schema version. Should almost always be 0 (maybe bump on release michael@0: // branches if we have to). michael@0: const uint32_t kMinorSchemaVersion = 0; michael@0: michael@0: // The schema version we store in the SQLite database is a (signed) 32-bit michael@0: // integer. The major version is left-shifted 4 bits so the max value is michael@0: // 0xFFFFFFF. The minor version occupies the lower 4 bits and its max is 0xF. michael@0: static_assert(kMajorSchemaVersion <= 0xFFFFFFF, michael@0: "Major version needs to fit in 28 bits."); michael@0: static_assert(kMinorSchemaVersion <= 0xF, michael@0: "Minor version needs to fit in 4 bits."); michael@0: michael@0: inline michael@0: int32_t michael@0: MakeSchemaVersion(uint32_t aMajorSchemaVersion, michael@0: uint32_t aMinorSchemaVersion) michael@0: { michael@0: return int32_t((aMajorSchemaVersion << 4) + aMinorSchemaVersion); michael@0: } michael@0: michael@0: const int32_t kSQLiteSchemaVersion = int32_t((kMajorSchemaVersion << 4) + michael@0: kMinorSchemaVersion); michael@0: michael@0: const uint32_t kGoldenRatioU32 = 0x9E3779B9U; michael@0: michael@0: inline michael@0: uint32_t michael@0: RotateBitsLeft32(uint32_t value, uint8_t bits) michael@0: { michael@0: MOZ_ASSERT(bits < 32); michael@0: return (value << bits) | (value >> (32 - bits)); michael@0: } michael@0: michael@0: inline michael@0: uint32_t michael@0: HashName(const nsAString& aName) michael@0: { michael@0: const char16_t* str = aName.BeginReading(); michael@0: size_t length = aName.Length(); michael@0: michael@0: uint32_t hash = 0; michael@0: for (size_t i = 0; i < length; i++) { michael@0: hash = kGoldenRatioU32 * (RotateBitsLeft32(hash, 5) ^ str[i]); michael@0: } michael@0: michael@0: return hash; michael@0: } michael@0: michael@0: nsresult michael@0: GetDatabaseFilename(const nsAString& aName, michael@0: nsAString& aDatabaseFilename) michael@0: { michael@0: aDatabaseFilename.AppendInt(HashName(aName)); michael@0: michael@0: nsCString escapedName; michael@0: if (!NS_Escape(NS_ConvertUTF16toUTF8(aName), escapedName, url_XPAlphas)) { michael@0: NS_WARNING("Can't escape database name!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: const char* forwardIter = escapedName.BeginReading(); michael@0: const char* backwardIter = escapedName.EndReading() - 1; michael@0: michael@0: nsCString substring; michael@0: while (forwardIter <= backwardIter && substring.Length() < 21) { michael@0: if (substring.Length() % 2) { michael@0: substring.Append(*backwardIter--); michael@0: } michael@0: else { michael@0: substring.Append(*forwardIter++); michael@0: } michael@0: } michael@0: michael@0: aDatabaseFilename.Append(NS_ConvertASCIItoUTF16(substring)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CreateFileTables(mozIStorageConnection* aDBConn) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "CreateFileTables"); michael@0: michael@0: // Table `file` michael@0: nsresult rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE file (" michael@0: "id INTEGER PRIMARY KEY, " michael@0: "refcount INTEGER NOT NULL" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TRIGGER object_data_insert_trigger " michael@0: "AFTER INSERT ON object_data " michael@0: "FOR EACH ROW " michael@0: "WHEN NEW.file_ids IS NOT NULL " michael@0: "BEGIN " michael@0: "SELECT update_refcount(NULL, NEW.file_ids); " michael@0: "END;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TRIGGER object_data_update_trigger " michael@0: "AFTER UPDATE OF file_ids ON object_data " michael@0: "FOR EACH ROW " michael@0: "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL " michael@0: "BEGIN " michael@0: "SELECT update_refcount(OLD.file_ids, NEW.file_ids); " michael@0: "END;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TRIGGER object_data_delete_trigger " michael@0: "AFTER DELETE ON object_data " michael@0: "FOR EACH ROW WHEN OLD.file_ids IS NOT NULL " michael@0: "BEGIN " michael@0: "SELECT update_refcount(OLD.file_ids, NULL); " michael@0: "END;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TRIGGER file_update_trigger " michael@0: "AFTER UPDATE ON file " michael@0: "FOR EACH ROW WHEN NEW.refcount = 0 " michael@0: "BEGIN " michael@0: "DELETE FROM file WHERE id = OLD.id; " michael@0: "END;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: CreateTables(mozIStorageConnection* aDBConn) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: NS_ASSERTION(aDBConn, "Passing a null database connection!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "CreateTables"); michael@0: michael@0: // Table `database` michael@0: nsresult rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE database (" michael@0: "name TEXT NOT NULL, " michael@0: "version INTEGER NOT NULL DEFAULT 0" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Table `object_store` michael@0: rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE object_store (" michael@0: "id INTEGER PRIMARY KEY, " michael@0: "auto_increment INTEGER NOT NULL DEFAULT 0, " michael@0: "name TEXT NOT NULL, " michael@0: "key_path TEXT, " michael@0: "UNIQUE (name)" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Table `object_data` michael@0: rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE object_data (" michael@0: "id INTEGER PRIMARY KEY, " michael@0: "object_store_id INTEGER NOT NULL, " michael@0: "key_value BLOB DEFAULT NULL, " michael@0: "file_ids TEXT, " michael@0: "data BLOB NOT NULL, " michael@0: "UNIQUE (object_store_id, key_value), " michael@0: "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Table `index` michael@0: rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE object_store_index (" michael@0: "id INTEGER PRIMARY KEY, " michael@0: "object_store_id INTEGER NOT NULL, " michael@0: "name TEXT NOT NULL, " michael@0: "key_path TEXT NOT NULL, " michael@0: "unique_index INTEGER NOT NULL, " michael@0: "multientry INTEGER NOT NULL, " michael@0: "UNIQUE (object_store_id, name), " michael@0: "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Table `index_data` michael@0: rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE index_data (" michael@0: "index_id INTEGER NOT NULL, " michael@0: "value BLOB NOT NULL, " michael@0: "object_data_key BLOB NOT NULL, " michael@0: "object_data_id INTEGER NOT NULL, " michael@0: "PRIMARY KEY (index_id, value, object_data_key), " michael@0: "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " michael@0: "CASCADE, " michael@0: "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Need this to make cascading deletes from object_data and object_store fast. michael@0: rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE INDEX index_data_object_data_id_index " michael@0: "ON index_data (object_data_id);" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Table `unique_index_data` michael@0: rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE unique_index_data (" michael@0: "index_id INTEGER NOT NULL, " michael@0: "value BLOB NOT NULL, " michael@0: "object_data_key BLOB NOT NULL, " michael@0: "object_data_id INTEGER NOT NULL, " michael@0: "PRIMARY KEY (index_id, value, object_data_key), " michael@0: "UNIQUE (index_id, value), " michael@0: "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " michael@0: "CASCADE " michael@0: "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Need this to make cascading deletes from object_data and object_store fast. michael@0: rv = aDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE INDEX unique_index_data_object_data_id_index " michael@0: "ON unique_index_data (object_data_id);" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = CreateFileTables(aDBConn); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aDBConn->SetSchemaVersion(kSQLiteSchemaVersion); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: UpgradeSchemaFrom4To5(mozIStorageConnection* aConnection) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "UpgradeSchemaFrom4To5"); michael@0: michael@0: nsresult rv; michael@0: michael@0: // All we changed is the type of the version column, so lets try to michael@0: // convert that to an integer, and if we fail, set it to 0. michael@0: nsCOMPtr stmt; michael@0: rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT name, version, dataVersion " michael@0: "FROM database" michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsString name; michael@0: int32_t intVersion; michael@0: int64_t dataVersion; michael@0: michael@0: { michael@0: mozStorageStatementScoper scoper(stmt); michael@0: michael@0: bool hasResults; michael@0: rv = stmt->ExecuteStep(&hasResults); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_TRUE(hasResults, NS_ERROR_FAILURE); michael@0: michael@0: nsString version; michael@0: rv = stmt->GetString(1, version); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: intVersion = version.ToInteger(&rv); michael@0: if (NS_FAILED(rv)) { michael@0: intVersion = 0; michael@0: } michael@0: michael@0: rv = stmt->GetString(0, name); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->GetInt64(2, &dataVersion); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE database" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE database (" michael@0: "name TEXT NOT NULL, " michael@0: "version INTEGER NOT NULL DEFAULT 0, " michael@0: "dataVersion INTEGER NOT NULL" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( michael@0: "INSERT INTO database (name, version, dataVersion) " michael@0: "VALUES (:name, :version, :dataVersion)" michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: { michael@0: mozStorageStatementScoper scoper(stmt); michael@0: michael@0: rv = stmt->BindStringParameter(0, name); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->BindInt32Parameter(1, intVersion); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->BindInt64Parameter(2, dataVersion); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = aConnection->SetSchemaVersion(5); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: UpgradeSchemaFrom5To6(mozIStorageConnection* aConnection) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "UpgradeSchemaFrom5To6"); michael@0: michael@0: // First, drop all the indexes we're no longer going to use. michael@0: nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP INDEX key_index;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP INDEX ai_key_index;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP INDEX value_index;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP INDEX ai_value_index;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Now, reorder the columns of object_data to put the blob data last. We do michael@0: // this by copying into a temporary table, dropping the original, then copying michael@0: // back into a newly created table. michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TEMPORARY TABLE temp_upgrade (" michael@0: "id INTEGER PRIMARY KEY, " michael@0: "object_store_id, " michael@0: "key_value, " michael@0: "data " michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO temp_upgrade " michael@0: "SELECT id, object_store_id, key_value, data " michael@0: "FROM object_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE object_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE object_data (" michael@0: "id INTEGER PRIMARY KEY, " michael@0: "object_store_id INTEGER NOT NULL, " michael@0: "key_value DEFAULT NULL, " michael@0: "data BLOB NOT NULL, " michael@0: "UNIQUE (object_store_id, key_value), " michael@0: "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO object_data " michael@0: "SELECT id, object_store_id, key_value, data " michael@0: "FROM temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We need to add a unique constraint to our ai_object_data table. Copy all michael@0: // the data out of it using a temporary table as before. michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TEMPORARY TABLE temp_upgrade (" michael@0: "id INTEGER PRIMARY KEY, " michael@0: "object_store_id, " michael@0: "data " michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO temp_upgrade " michael@0: "SELECT id, object_store_id, data " michael@0: "FROM ai_object_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE ai_object_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE ai_object_data (" michael@0: "id INTEGER PRIMARY KEY AUTOINCREMENT, " michael@0: "object_store_id INTEGER NOT NULL, " michael@0: "data BLOB NOT NULL, " michael@0: "UNIQUE (object_store_id, id), " michael@0: "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO ai_object_data " michael@0: "SELECT id, object_store_id, data " michael@0: "FROM temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Fix up the index_data table. We're reordering the columns as well as michael@0: // changing the primary key from being a simple id to being a composite. michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TEMPORARY TABLE temp_upgrade (" michael@0: "index_id, " michael@0: "value, " michael@0: "object_data_key, " michael@0: "object_data_id " michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO temp_upgrade " michael@0: "SELECT index_id, value, object_data_key, object_data_id " michael@0: "FROM index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE index_data (" michael@0: "index_id INTEGER NOT NULL, " michael@0: "value NOT NULL, " michael@0: "object_data_key NOT NULL, " michael@0: "object_data_id INTEGER NOT NULL, " michael@0: "PRIMARY KEY (index_id, value, object_data_key), " michael@0: "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " michael@0: "CASCADE, " michael@0: "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT OR IGNORE INTO index_data " michael@0: "SELECT index_id, value, object_data_key, object_data_id " michael@0: "FROM temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE INDEX index_data_object_data_id_index " michael@0: "ON index_data (object_data_id);" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Fix up the unique_index_data table. We're reordering the columns as well as michael@0: // changing the primary key from being a simple id to being a composite. michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TEMPORARY TABLE temp_upgrade (" michael@0: "index_id, " michael@0: "value, " michael@0: "object_data_key, " michael@0: "object_data_id " michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO temp_upgrade " michael@0: "SELECT index_id, value, object_data_key, object_data_id " michael@0: "FROM unique_index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE unique_index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE unique_index_data (" michael@0: "index_id INTEGER NOT NULL, " michael@0: "value NOT NULL, " michael@0: "object_data_key NOT NULL, " michael@0: "object_data_id INTEGER NOT NULL, " michael@0: "PRIMARY KEY (index_id, value, object_data_key), " michael@0: "UNIQUE (index_id, value), " michael@0: "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " michael@0: "CASCADE " michael@0: "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO unique_index_data " michael@0: "SELECT index_id, value, object_data_key, object_data_id " michael@0: "FROM temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE INDEX unique_index_data_object_data_id_index " michael@0: "ON unique_index_data (object_data_id);" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Fix up the ai_index_data table. We're reordering the columns as well as michael@0: // changing the primary key from being a simple id to being a composite. michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TEMPORARY TABLE temp_upgrade (" michael@0: "index_id, " michael@0: "value, " michael@0: "ai_object_data_id " michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO temp_upgrade " michael@0: "SELECT index_id, value, ai_object_data_id " michael@0: "FROM ai_index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE ai_index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE ai_index_data (" michael@0: "index_id INTEGER NOT NULL, " michael@0: "value NOT NULL, " michael@0: "ai_object_data_id INTEGER NOT NULL, " michael@0: "PRIMARY KEY (index_id, value, ai_object_data_id), " michael@0: "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " michael@0: "CASCADE, " michael@0: "FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT OR IGNORE INTO ai_index_data " michael@0: "SELECT index_id, value, ai_object_data_id " michael@0: "FROM temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE INDEX ai_index_data_ai_object_data_id_index " michael@0: "ON ai_index_data (ai_object_data_id);" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Fix up the ai_unique_index_data table. We're reordering the columns as well michael@0: // as changing the primary key from being a simple id to being a composite. michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TEMPORARY TABLE temp_upgrade (" michael@0: "index_id, " michael@0: "value, " michael@0: "ai_object_data_id " michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO temp_upgrade " michael@0: "SELECT index_id, value, ai_object_data_id " michael@0: "FROM ai_unique_index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE ai_unique_index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE ai_unique_index_data (" michael@0: "index_id INTEGER NOT NULL, " michael@0: "value NOT NULL, " michael@0: "ai_object_data_id INTEGER NOT NULL, " michael@0: "UNIQUE (index_id, value), " michael@0: "PRIMARY KEY (index_id, value, ai_object_data_id), " michael@0: "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " michael@0: "CASCADE, " michael@0: "FOREIGN KEY (ai_object_data_id) REFERENCES ai_object_data(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO ai_unique_index_data " michael@0: "SELECT index_id, value, ai_object_data_id " michael@0: "FROM temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE INDEX ai_unique_index_data_ai_object_data_id_index " michael@0: "ON ai_unique_index_data (ai_object_data_id);" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->SetSchemaVersion(6); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: UpgradeSchemaFrom6To7(mozIStorageConnection* aConnection) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "UpgradeSchemaFrom6To7"); michael@0: michael@0: nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TEMPORARY TABLE temp_upgrade (" michael@0: "id, " michael@0: "name, " michael@0: "key_path, " michael@0: "auto_increment" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO temp_upgrade " michael@0: "SELECT id, name, key_path, auto_increment " michael@0: "FROM object_store;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE object_store;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE object_store (" michael@0: "id INTEGER PRIMARY KEY, " michael@0: "auto_increment INTEGER NOT NULL DEFAULT 0, " michael@0: "name TEXT NOT NULL, " michael@0: "key_path TEXT, " michael@0: "UNIQUE (name)" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO object_store " michael@0: "SELECT id, auto_increment, name, nullif(key_path, '') " michael@0: "FROM temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->SetSchemaVersion(7); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: UpgradeSchemaFrom7To8(mozIStorageConnection* aConnection) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "UpgradeSchemaFrom7To8"); michael@0: michael@0: nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TEMPORARY TABLE temp_upgrade (" michael@0: "id, " michael@0: "object_store_id, " michael@0: "name, " michael@0: "key_path, " michael@0: "unique_index, " michael@0: "object_store_autoincrement" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO temp_upgrade " michael@0: "SELECT id, object_store_id, name, key_path, " michael@0: "unique_index, object_store_autoincrement " michael@0: "FROM object_store_index;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE object_store_index;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE object_store_index (" michael@0: "id INTEGER, " michael@0: "object_store_id INTEGER NOT NULL, " michael@0: "name TEXT NOT NULL, " michael@0: "key_path TEXT NOT NULL, " michael@0: "unique_index INTEGER NOT NULL, " michael@0: "multientry INTEGER NOT NULL, " michael@0: "object_store_autoincrement INTERGER NOT NULL, " michael@0: "PRIMARY KEY (id), " michael@0: "UNIQUE (object_store_id, name), " michael@0: "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO object_store_index " michael@0: "SELECT id, object_store_id, name, key_path, " michael@0: "unique_index, 0, object_store_autoincrement " michael@0: "FROM temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->SetSchemaVersion(8); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: class CompressDataBlobsFunction MOZ_FINAL : public mozIStorageFunction michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD michael@0: OnFunctionCall(mozIStorageValueArray* aArguments, michael@0: nsIVariant** aResult) michael@0: { michael@0: PROFILER_LABEL("IndexedDB", "CompressDataBlobsFunction::OnFunctionCall"); michael@0: michael@0: uint32_t argc; michael@0: nsresult rv = aArguments->GetNumEntries(&argc); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (argc != 1) { michael@0: NS_WARNING("Don't call me with the wrong number of arguments!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: int32_t type; michael@0: rv = aArguments->GetTypeOfIndex(0, &type); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (type != mozIStorageStatement::VALUE_TYPE_BLOB) { michael@0: NS_WARNING("Don't call me with the wrong type of arguments!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: const uint8_t* uncompressed; michael@0: uint32_t uncompressedLength; michael@0: rv = aArguments->GetSharedBlob(0, &uncompressedLength, &uncompressed); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength); michael@0: char* compressed = (char*)moz_malloc(compressedLength); michael@0: NS_ENSURE_TRUE(compressed, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: snappy::RawCompress(reinterpret_cast(uncompressed), michael@0: uncompressedLength, compressed, &compressedLength); michael@0: michael@0: std::pair data((uint8_t*)compressed, michael@0: int(compressedLength)); michael@0: // The variant takes ownership of | compressed |. michael@0: nsCOMPtr result = new mozilla::storage::AdoptedBlobVariant(data); michael@0: michael@0: result.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(CompressDataBlobsFunction, mozIStorageFunction) michael@0: michael@0: nsresult michael@0: UpgradeSchemaFrom8To9_0(mozIStorageConnection* aConnection) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "UpgradeSchemaFrom8To9_0"); michael@0: michael@0: // We no longer use the dataVersion column. michael@0: nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "UPDATE database SET dataVersion = 0;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr compressor = new CompressDataBlobsFunction(); michael@0: michael@0: NS_NAMED_LITERAL_CSTRING(compressorName, "compress"); michael@0: michael@0: rv = aConnection->CreateFunction(compressorName, 1, compressor); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Turn off foreign key constraints before we do anything here. michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "UPDATE object_data SET data = compress(data);" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "UPDATE ai_object_data SET data = compress(data);" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->RemoveFunction(compressorName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->SetSchemaVersion(MakeSchemaVersion(9, 0)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: UpgradeSchemaFrom9_0To10_0(mozIStorageConnection* aConnection) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "UpgradeSchemaFrom9_0To10_0"); michael@0: michael@0: nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "ALTER TABLE object_data ADD COLUMN file_ids TEXT;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "ALTER TABLE ai_object_data ADD COLUMN file_ids TEXT;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = CreateFileTables(aConnection); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->SetSchemaVersion(MakeSchemaVersion(10, 0)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: UpgradeSchemaFrom10_0To11_0(mozIStorageConnection* aConnection) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "UpgradeSchemaFrom10_0To11_0"); michael@0: michael@0: nsresult rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TEMPORARY TABLE temp_upgrade (" michael@0: "id, " michael@0: "object_store_id, " michael@0: "name, " michael@0: "key_path, " michael@0: "unique_index, " michael@0: "multientry" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO temp_upgrade " michael@0: "SELECT id, object_store_id, name, key_path, " michael@0: "unique_index, multientry " michael@0: "FROM object_store_index;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE object_store_index;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE object_store_index (" michael@0: "id INTEGER PRIMARY KEY, " michael@0: "object_store_id INTEGER NOT NULL, " michael@0: "name TEXT NOT NULL, " michael@0: "key_path TEXT NOT NULL, " michael@0: "unique_index INTEGER NOT NULL, " michael@0: "multientry INTEGER NOT NULL, " michael@0: "UNIQUE (object_store_id, name), " michael@0: "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO object_store_index " michael@0: "SELECT id, object_store_id, name, key_path, " michael@0: "unique_index, multientry " michael@0: "FROM temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TRIGGER object_data_insert_trigger;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO object_data (object_store_id, key_value, data, file_ids) " michael@0: "SELECT object_store_id, id, data, file_ids " michael@0: "FROM ai_object_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TRIGGER object_data_insert_trigger " michael@0: "AFTER INSERT ON object_data " michael@0: "FOR EACH ROW " michael@0: "WHEN NEW.file_ids IS NOT NULL " michael@0: "BEGIN " michael@0: "SELECT update_refcount(NULL, NEW.file_ids); " michael@0: "END;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO index_data (index_id, value, object_data_key, object_data_id) " michael@0: "SELECT ai_index_data.index_id, ai_index_data.value, ai_index_data.ai_object_data_id, object_data.id " michael@0: "FROM ai_index_data " michael@0: "INNER JOIN object_store_index ON " michael@0: "object_store_index.id = ai_index_data.index_id " michael@0: "INNER JOIN object_data ON " michael@0: "object_data.object_store_id = object_store_index.object_store_id AND " michael@0: "object_data.key_value = ai_index_data.ai_object_data_id;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO unique_index_data (index_id, value, object_data_key, object_data_id) " michael@0: "SELECT ai_unique_index_data.index_id, ai_unique_index_data.value, ai_unique_index_data.ai_object_data_id, object_data.id " michael@0: "FROM ai_unique_index_data " michael@0: "INNER JOIN object_store_index ON " michael@0: "object_store_index.id = ai_unique_index_data.index_id " michael@0: "INNER JOIN object_data ON " michael@0: "object_data.object_store_id = object_store_index.object_store_id AND " michael@0: "object_data.key_value = ai_unique_index_data.ai_object_data_id;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "UPDATE object_store " michael@0: "SET auto_increment = (SELECT max(id) FROM ai_object_data) + 1 " michael@0: "WHERE auto_increment;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE ai_unique_index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE ai_index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE ai_object_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->SetSchemaVersion(MakeSchemaVersion(11, 0)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: class EncodeKeysFunction MOZ_FINAL : public mozIStorageFunction michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD michael@0: OnFunctionCall(mozIStorageValueArray* aArguments, michael@0: nsIVariant** aResult) michael@0: { michael@0: PROFILER_LABEL("IndexedDB", "EncodeKeysFunction::OnFunctionCall"); michael@0: michael@0: uint32_t argc; michael@0: nsresult rv = aArguments->GetNumEntries(&argc); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (argc != 1) { michael@0: NS_WARNING("Don't call me with the wrong number of arguments!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: int32_t type; michael@0: rv = aArguments->GetTypeOfIndex(0, &type); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: Key key; michael@0: if (type == mozIStorageStatement::VALUE_TYPE_INTEGER) { michael@0: int64_t intKey; michael@0: aArguments->GetInt64(0, &intKey); michael@0: key.SetFromInteger(intKey); michael@0: } michael@0: else if (type == mozIStorageStatement::VALUE_TYPE_TEXT) { michael@0: nsString stringKey; michael@0: aArguments->GetString(0, stringKey); michael@0: key.SetFromString(stringKey); michael@0: } michael@0: else { michael@0: NS_WARNING("Don't call me with the wrong type of arguments!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: const nsCString& buffer = key.GetBuffer(); michael@0: michael@0: std::pair data(static_cast(buffer.get()), michael@0: int(buffer.Length())); michael@0: michael@0: nsCOMPtr result = new mozilla::storage::BlobVariant(data); michael@0: michael@0: result.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(EncodeKeysFunction, mozIStorageFunction) michael@0: michael@0: nsresult michael@0: UpgradeSchemaFrom11_0To12_0(mozIStorageConnection* aConnection) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "UpgradeSchemaFrom11_0To12_0"); michael@0: michael@0: NS_NAMED_LITERAL_CSTRING(encoderName, "encode"); michael@0: michael@0: nsCOMPtr encoder = new EncodeKeysFunction(); michael@0: michael@0: nsresult rv = aConnection->CreateFunction(encoderName, 1, encoder); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TEMPORARY TABLE temp_upgrade (" michael@0: "id INTEGER PRIMARY KEY, " michael@0: "object_store_id, " michael@0: "key_value, " michael@0: "data, " michael@0: "file_ids " michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO temp_upgrade " michael@0: "SELECT id, object_store_id, encode(key_value), data, file_ids " michael@0: "FROM object_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE object_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE object_data (" michael@0: "id INTEGER PRIMARY KEY, " michael@0: "object_store_id INTEGER NOT NULL, " michael@0: "key_value BLOB DEFAULT NULL, " michael@0: "file_ids TEXT, " michael@0: "data BLOB NOT NULL, " michael@0: "UNIQUE (object_store_id, key_value), " michael@0: "FOREIGN KEY (object_store_id) REFERENCES object_store(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO object_data " michael@0: "SELECT id, object_store_id, key_value, file_ids, data " michael@0: "FROM temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TRIGGER object_data_insert_trigger " michael@0: "AFTER INSERT ON object_data " michael@0: "FOR EACH ROW " michael@0: "WHEN NEW.file_ids IS NOT NULL " michael@0: "BEGIN " michael@0: "SELECT update_refcount(NULL, NEW.file_ids); " michael@0: "END;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TRIGGER object_data_update_trigger " michael@0: "AFTER UPDATE OF file_ids ON object_data " michael@0: "FOR EACH ROW " michael@0: "WHEN OLD.file_ids IS NOT NULL OR NEW.file_ids IS NOT NULL " michael@0: "BEGIN " michael@0: "SELECT update_refcount(OLD.file_ids, NEW.file_ids); " michael@0: "END;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TRIGGER object_data_delete_trigger " michael@0: "AFTER DELETE ON object_data " michael@0: "FOR EACH ROW WHEN OLD.file_ids IS NOT NULL " michael@0: "BEGIN " michael@0: "SELECT update_refcount(OLD.file_ids, NULL); " michael@0: "END;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TEMPORARY TABLE temp_upgrade (" michael@0: "index_id, " michael@0: "value, " michael@0: "object_data_key, " michael@0: "object_data_id " michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO temp_upgrade " michael@0: "SELECT index_id, encode(value), encode(object_data_key), object_data_id " michael@0: "FROM index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE index_data (" michael@0: "index_id INTEGER NOT NULL, " michael@0: "value BLOB NOT NULL, " michael@0: "object_data_key BLOB NOT NULL, " michael@0: "object_data_id INTEGER NOT NULL, " michael@0: "PRIMARY KEY (index_id, value, object_data_key), " michael@0: "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " michael@0: "CASCADE, " michael@0: "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO index_data " michael@0: "SELECT index_id, value, object_data_key, object_data_id " michael@0: "FROM temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE INDEX index_data_object_data_id_index " michael@0: "ON index_data (object_data_id);" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TEMPORARY TABLE temp_upgrade (" michael@0: "index_id, " michael@0: "value, " michael@0: "object_data_key, " michael@0: "object_data_id " michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO temp_upgrade " michael@0: "SELECT index_id, encode(value), encode(object_data_key), object_data_id " michael@0: "FROM unique_index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE unique_index_data;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE TABLE unique_index_data (" michael@0: "index_id INTEGER NOT NULL, " michael@0: "value BLOB NOT NULL, " michael@0: "object_data_key BLOB NOT NULL, " michael@0: "object_data_id INTEGER NOT NULL, " michael@0: "PRIMARY KEY (index_id, value, object_data_key), " michael@0: "UNIQUE (index_id, value), " michael@0: "FOREIGN KEY (index_id) REFERENCES object_store_index(id) ON DELETE " michael@0: "CASCADE " michael@0: "FOREIGN KEY (object_data_id) REFERENCES object_data(id) ON DELETE " michael@0: "CASCADE" michael@0: ");" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "INSERT INTO unique_index_data " michael@0: "SELECT index_id, value, object_data_key, object_data_id " michael@0: "FROM temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE temp_upgrade;" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "CREATE INDEX unique_index_data_object_data_id_index " michael@0: "ON unique_index_data (object_data_id);" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->RemoveFunction(encoderName); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aConnection->SetSchemaVersion(MakeSchemaVersion(12, 0)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: UpgradeSchemaFrom12_0To13_0(mozIStorageConnection* aConnection, michael@0: bool* aVacuumNeeded) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "UpgradeSchemaFrom12_0To13_0"); michael@0: michael@0: nsresult rv; michael@0: michael@0: #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) michael@0: int32_t defaultPageSize; michael@0: rv = aConnection->GetDefaultPageSize(&defaultPageSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Enable auto_vacuum mode and update the page size to the platform default. michael@0: nsAutoCString upgradeQuery("PRAGMA auto_vacuum = FULL; PRAGMA page_size = "); michael@0: upgradeQuery.AppendInt(defaultPageSize); michael@0: michael@0: rv = aConnection->ExecuteSimpleSQL(upgradeQuery); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aVacuumNeeded = true; michael@0: #endif michael@0: michael@0: rv = aConnection->SetSchemaVersion(MakeSchemaVersion(13, 0)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: UpgradeSchemaFrom13_0To14_0(mozIStorageConnection* aConnection) michael@0: { michael@0: // The only change between 13 and 14 was a different structured michael@0: // clone format, but it's backwards-compatible. michael@0: nsresult rv = aConnection->SetSchemaVersion(MakeSchemaVersion(14, 0)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: class VersionChangeEventsRunnable; michael@0: michael@0: class SetVersionHelper : public AsyncConnectionHelper, michael@0: public IDBTransactionListener, michael@0: public AcquireListener michael@0: { michael@0: friend class VersionChangeEventsRunnable; michael@0: michael@0: public: michael@0: SetVersionHelper(IDBTransaction* aTransaction, michael@0: IDBOpenDBRequest* aRequest, michael@0: OpenDatabaseHelper* aHelper, michael@0: uint64_t aRequestedVersion, michael@0: uint64_t aCurrentVersion) michael@0: : AsyncConnectionHelper(aTransaction, aRequest), michael@0: mOpenRequest(aRequest), mOpenHelper(aHelper), michael@0: mRequestedVersion(aRequestedVersion), michael@0: mCurrentVersion(aCurrentVersion) michael@0: { michael@0: mTransaction->SetTransactionListener(this); michael@0: } michael@0: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: virtual nsresult GetSuccessResult(JSContext* aCx, michael@0: JS::MutableHandle aVal) MOZ_OVERRIDE; michael@0: michael@0: virtual nsresult michael@0: OnExclusiveAccessAcquired() MOZ_OVERRIDE; michael@0: michael@0: protected: michael@0: virtual nsresult Init() MOZ_OVERRIDE; michael@0: michael@0: virtual nsresult DoDatabaseWork(mozIStorageConnection* aConnection) michael@0: MOZ_OVERRIDE; michael@0: michael@0: // SetVersionHelper never fires an error event at the request. It hands that michael@0: // responsibility back to the OpenDatabaseHelper michael@0: virtual void OnError() MOZ_OVERRIDE michael@0: { } michael@0: michael@0: // Need an upgradeneeded event here. michael@0: virtual already_AddRefed CreateSuccessEvent( michael@0: mozilla::dom::EventTarget* aOwner) MOZ_OVERRIDE; michael@0: michael@0: virtual nsresult NotifyTransactionPreComplete(IDBTransaction* aTransaction) michael@0: MOZ_OVERRIDE; michael@0: virtual nsresult NotifyTransactionPostComplete(IDBTransaction* aTransaction) michael@0: MOZ_OVERRIDE; michael@0: michael@0: virtual ChildProcessSendResult michael@0: SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE michael@0: { michael@0: return Success_NotSent; michael@0: } michael@0: michael@0: virtual nsresult UnpackResponseFromParentProcess( michael@0: const ResponseValue& aResponseValue) michael@0: MOZ_OVERRIDE michael@0: { michael@0: MOZ_CRASH("Should never get here!"); michael@0: } michael@0: michael@0: uint64_t RequestedVersion() const michael@0: { michael@0: return mRequestedVersion; michael@0: } michael@0: michael@0: private: michael@0: // In-params michael@0: nsRefPtr mOpenRequest; michael@0: nsRefPtr mOpenHelper; michael@0: uint64_t mRequestedVersion; michael@0: uint64_t mCurrentVersion; michael@0: }; michael@0: michael@0: class DeleteDatabaseHelper : public AsyncConnectionHelper, michael@0: public AcquireListener michael@0: { michael@0: friend class VersionChangeEventsRunnable; michael@0: public: michael@0: DeleteDatabaseHelper(IDBOpenDBRequest* aRequest, michael@0: OpenDatabaseHelper* aHelper, michael@0: uint64_t aCurrentVersion, michael@0: const nsAString& aName, michael@0: const nsACString& aGroup, michael@0: const nsACString& aASCIIOrigin, michael@0: PersistenceType aPersistenceType) michael@0: : AsyncConnectionHelper(static_cast(nullptr), aRequest), michael@0: mOpenHelper(aHelper), mOpenRequest(aRequest), michael@0: mCurrentVersion(aCurrentVersion), mName(aName), michael@0: mGroup(aGroup), mASCIIOrigin(aASCIIOrigin), michael@0: mPersistenceType(aPersistenceType) michael@0: { } michael@0: michael@0: NS_DECL_ISUPPORTS_INHERITED michael@0: michael@0: nsresult GetSuccessResult(JSContext* aCx, michael@0: JS::MutableHandle aVal); michael@0: michael@0: void ReleaseMainThreadObjects() michael@0: { michael@0: mOpenHelper = nullptr; michael@0: mOpenRequest = nullptr; michael@0: michael@0: AsyncConnectionHelper::ReleaseMainThreadObjects(); michael@0: } michael@0: michael@0: virtual nsresult michael@0: OnExclusiveAccessAcquired() MOZ_OVERRIDE; michael@0: michael@0: protected: michael@0: nsresult DoDatabaseWork(mozIStorageConnection* aConnection); michael@0: nsresult Init(); michael@0: michael@0: // DeleteDatabaseHelper never fires events at the request. It hands that michael@0: // responsibility back to the OpenDatabaseHelper michael@0: void OnError() michael@0: { michael@0: mOpenHelper->NotifyDeleteFinished(); michael@0: } michael@0: michael@0: nsresult OnSuccess() michael@0: { michael@0: return mOpenHelper->NotifyDeleteFinished(); michael@0: } michael@0: michael@0: uint64_t RequestedVersion() const michael@0: { michael@0: return 0; michael@0: } michael@0: michael@0: virtual ChildProcessSendResult michael@0: SendResponseToChildProcess(nsresult aResultCode) MOZ_OVERRIDE michael@0: { michael@0: return Success_NotSent; michael@0: } michael@0: michael@0: virtual nsresult UnpackResponseFromParentProcess( michael@0: const ResponseValue& aResponseValue) michael@0: MOZ_OVERRIDE michael@0: { michael@0: MOZ_CRASH("Should never get here!"); michael@0: } michael@0: michael@0: private: michael@0: // In-params michael@0: nsRefPtr mOpenHelper; michael@0: nsRefPtr mOpenRequest; michael@0: uint64_t mCurrentVersion; michael@0: nsString mName; michael@0: nsCString mGroup; michael@0: nsCString mASCIIOrigin; michael@0: PersistenceType mPersistenceType; michael@0: }; michael@0: michael@0: // Responsible for firing "versionchange" events at all live and non-closed michael@0: // databases, and for firing a "blocked" event at the requesting database if any michael@0: // databases fail to close. michael@0: class VersionChangeEventsRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: VersionChangeEventsRunnable( michael@0: IDBDatabase* aRequestingDatabase, michael@0: IDBOpenDBRequest* aRequest, michael@0: nsTArray >& aWaitingDatabases, michael@0: int64_t aOldVersion, michael@0: int64_t aNewVersion) michael@0: : mRequestingDatabase(aRequestingDatabase), michael@0: mRequest(aRequest), michael@0: mOldVersion(aOldVersion), michael@0: mNewVersion(aNewVersion) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aRequestingDatabase, "Null pointer!"); michael@0: NS_ASSERTION(aRequest, "Null pointer!"); michael@0: michael@0: mWaitingDatabases.SwapElements(aWaitingDatabases); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: PROFILER_MAIN_THREAD_LABEL("IndexedDB", "VersionChangeEventsRunnable::Run"); michael@0: michael@0: // Fire version change events at all of the databases that are not already michael@0: // closed. Also kick bfcached documents out of bfcache. michael@0: uint32_t count = mWaitingDatabases.Length(); michael@0: for (uint32_t index = 0; index < count; index++) { michael@0: IDBDatabase* database = michael@0: IDBDatabase::FromStorage(mWaitingDatabases[index]); michael@0: NS_ASSERTION(database, "This shouldn't be null!"); michael@0: michael@0: if (database->IsClosed()) { michael@0: continue; michael@0: } michael@0: michael@0: // First check if the document the IDBDatabase is part of is bfcached. michael@0: nsCOMPtr ownerDoc = database->GetOwnerDocument(); michael@0: nsIBFCacheEntry* bfCacheEntry; michael@0: if (ownerDoc && (bfCacheEntry = ownerDoc->GetBFCacheEntry())) { michael@0: bfCacheEntry->RemoveFromBFCacheSync(); michael@0: NS_ASSERTION(database->IsClosed(), michael@0: "Kicking doc out of bfcache should have closed database"); michael@0: continue; michael@0: } michael@0: michael@0: // Next check if it's in the process of being bfcached. michael@0: nsPIDOMWindow* owner = database->GetOwner(); michael@0: if (owner && owner->IsFrozen()) { michael@0: // We can't kick the document out of the bfcache because it's not yet michael@0: // fully in the bfcache. Instead we'll abort everything for the window michael@0: // and mark it as not-bfcacheable. michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Huh?"); michael@0: quotaManager->AbortCloseStoragesForWindow(owner); michael@0: michael@0: NS_ASSERTION(database->IsClosed(), michael@0: "AbortCloseStoragesForWindow should have closed database"); michael@0: ownerDoc->DisallowBFCaching(); michael@0: continue; michael@0: } michael@0: michael@0: // Otherwise fire a versionchange event. michael@0: nsRefPtr event = michael@0: IDBVersionChangeEvent::Create(database, mOldVersion, mNewVersion); michael@0: NS_ENSURE_TRUE(event, NS_ERROR_FAILURE); michael@0: michael@0: bool dummy; michael@0: database->DispatchEvent(event, &dummy); michael@0: } michael@0: michael@0: // Now check to see if any didn't close. If there are some running still michael@0: // then fire the blocked event. michael@0: for (uint32_t index = 0; index < count; index++) { michael@0: if (!mWaitingDatabases[index]->IsClosed()) { michael@0: nsRefPtr event = michael@0: IDBVersionChangeEvent::CreateBlocked(mRequest, michael@0: mOldVersion, mNewVersion); michael@0: NS_ENSURE_TRUE(event, NS_ERROR_FAILURE); michael@0: michael@0: bool dummy; michael@0: mRequest->DispatchEvent(event, &dummy); michael@0: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: template michael@0: static michael@0: void QueueVersionChange(nsTArray >& aDatabases, michael@0: void* aClosure); michael@0: private: michael@0: nsRefPtr mRequestingDatabase; michael@0: nsRefPtr mRequest; michael@0: nsTArray > mWaitingDatabases; michael@0: int64_t mOldVersion; michael@0: int64_t mNewVersion; michael@0: }; michael@0: michael@0: } // anonymous namespace michael@0: michael@0: NS_IMPL_ISUPPORTS(OpenDatabaseHelper, nsIRunnable) michael@0: michael@0: nsresult michael@0: OpenDatabaseHelper::Init() michael@0: { michael@0: QuotaManager::GetStorageId(mPersistenceType, mASCIIOrigin, Client::IDB, michael@0: mName, mDatabaseId); michael@0: MOZ_ASSERT(!mDatabaseId.IsEmpty()); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: OpenDatabaseHelper::WaitForOpenAllowed() michael@0: { michael@0: NS_ASSERTION(mState == eCreated, "We've already been dispatched?"); michael@0: NS_ASSERTION(NS_IsMainThread(), "All hell is about to break lose!"); michael@0: michael@0: mState = eOpenPending; michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never be null!"); michael@0: michael@0: return quotaManager-> michael@0: WaitForOpenAllowed(OriginOrPatternString::FromOrigin(mASCIIOrigin), michael@0: Nullable(mPersistenceType), mDatabaseId, michael@0: this); michael@0: } michael@0: michael@0: nsresult michael@0: OpenDatabaseHelper::Dispatch(nsIEventTarget* aTarget) michael@0: { michael@0: NS_ASSERTION(mState == eCreated || mState == eOpenPending, michael@0: "We've already been dispatched?"); michael@0: michael@0: mState = eDBWork; michael@0: michael@0: return aTarget->Dispatch(this, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: nsresult michael@0: OpenDatabaseHelper::DispatchToIOThread() michael@0: { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never be null!"); michael@0: michael@0: return Dispatch(quotaManager->IOThread()); michael@0: } michael@0: michael@0: nsresult michael@0: OpenDatabaseHelper::RunImmediately() michael@0: { michael@0: NS_ASSERTION(mState == eCreated || mState == eOpenPending, michael@0: "We've already been dispatched?"); michael@0: NS_ASSERTION(NS_FAILED(mResultCode), michael@0: "Should only be short-circuiting if we failed!"); michael@0: NS_ASSERTION(NS_IsMainThread(), "All hell is about to break lose!"); michael@0: michael@0: mState = eFiringEvents; michael@0: michael@0: return this->Run(); michael@0: } michael@0: michael@0: nsresult michael@0: OpenDatabaseHelper::DoDatabaseWork() michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "OpenDatabaseHelper::DoDatabaseWork"); michael@0: michael@0: mState = eFiringEvents; // In case we fail somewhere along the line. michael@0: michael@0: if (QuotaManager::IsShuttingDown()) { michael@0: IDB_REPORT_INTERNAL_ERR(); michael@0: return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; michael@0: } michael@0: michael@0: NS_ASSERTION(mOpenDBRequest, "This should never be null!"); michael@0: michael@0: // This will be null for non-window contexts. michael@0: nsPIDOMWindow* window = mOpenDBRequest->GetOwner(); michael@0: michael@0: AutoEnterWindow autoWindow(window); michael@0: michael@0: nsCOMPtr dbDirectory; michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never be null!"); michael@0: michael@0: nsresult rv = michael@0: quotaManager->EnsureOriginIsInitialized(mPersistenceType, mGroup, michael@0: mASCIIOrigin, mTrackingQuota, michael@0: getter_AddRefs(dbDirectory)); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = dbDirectory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME)); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: bool exists; michael@0: rv = dbDirectory->Exists(&exists); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: if (!exists) { michael@0: rv = dbDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: } michael@0: #ifdef DEBUG michael@0: else { michael@0: bool isDirectory; michael@0: NS_ASSERTION(NS_SUCCEEDED(dbDirectory->IsDirectory(&isDirectory)) && michael@0: isDirectory, "Should have caught this earlier!"); michael@0: } michael@0: #endif michael@0: michael@0: nsAutoString filename; michael@0: rv = GetDatabaseFilename(mName, filename); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: nsCOMPtr dbFile; michael@0: rv = dbDirectory->Clone(getter_AddRefs(dbFile)); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = dbFile->Append(filename + NS_LITERAL_STRING(".sqlite")); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = dbFile->GetPath(mDatabaseFilePath); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: nsCOMPtr fmDirectory; michael@0: rv = dbDirectory->Clone(getter_AddRefs(fmDirectory)); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = fmDirectory->Append(filename); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: nsCOMPtr connection; michael@0: rv = CreateDatabaseConnection(dbFile, fmDirectory, mName, mPersistenceType, michael@0: mGroup, mASCIIOrigin, michael@0: getter_AddRefs(connection)); michael@0: if (NS_FAILED(rv) && michael@0: NS_ERROR_GET_MODULE(rv) != NS_ERROR_MODULE_DOM_INDEXEDDB) { michael@0: IDB_REPORT_INTERNAL_ERR(); michael@0: rv = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = IDBFactory::LoadDatabaseInformation(connection, mDatabaseId, michael@0: &mCurrentVersion, mObjectStores); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: if (mForDeletion) { michael@0: mState = eDeletePending; michael@0: return NS_OK; michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < mObjectStores.Length(); i++) { michael@0: nsRefPtr& objectStoreInfo = mObjectStores[i]; michael@0: for (uint32_t j = 0; j < objectStoreInfo->indexes.Length(); j++) { michael@0: IndexInfo& indexInfo = objectStoreInfo->indexes[j]; michael@0: mLastIndexId = std::max(indexInfo.id, mLastIndexId); michael@0: } michael@0: mLastObjectStoreId = std::max(objectStoreInfo->id, mLastObjectStoreId); michael@0: } michael@0: michael@0: // See if we need to do a VERSION_CHANGE transaction michael@0: michael@0: // Optional version semantics. michael@0: if (!mRequestedVersion) { michael@0: // If the requested version was not specified and the database was created, michael@0: // treat it as if version 1 were requested. michael@0: if (mCurrentVersion == 0) { michael@0: mRequestedVersion = 1; michael@0: } michael@0: else { michael@0: // Otherwise, treat it as if the current version were requested. michael@0: mRequestedVersion = mCurrentVersion; michael@0: } michael@0: } michael@0: michael@0: if (mCurrentVersion > mRequestedVersion) { michael@0: return NS_ERROR_DOM_INDEXEDDB_VERSION_ERR; michael@0: } michael@0: michael@0: if (mCurrentVersion != mRequestedVersion) { michael@0: mState = eSetVersionPending; michael@0: } michael@0: michael@0: IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); michael@0: NS_ASSERTION(mgr, "This should never be null!"); michael@0: michael@0: nsRefPtr fileManager = michael@0: mgr->GetFileManager(mPersistenceType, mASCIIOrigin, mName); michael@0: if (!fileManager) { michael@0: fileManager = new FileManager(mPersistenceType, mGroup, mASCIIOrigin, michael@0: mPrivilege, mName); michael@0: michael@0: rv = fileManager->Init(fmDirectory, connection); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: mgr->AddFileManager(fileManager); michael@0: } michael@0: michael@0: mFileManager = fileManager.forget(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: nsresult michael@0: OpenDatabaseHelper::CreateDatabaseConnection( michael@0: nsIFile* aDBFile, michael@0: nsIFile* aFMDirectory, michael@0: const nsAString& aName, michael@0: PersistenceType aPersistenceType, michael@0: const nsACString& aGroup, michael@0: const nsACString& aOrigin, michael@0: mozIStorageConnection** aConnection) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "OpenDatabaseHelper::CreateDatabaseConnection"); michael@0: michael@0: nsresult rv; michael@0: bool exists; michael@0: michael@0: if (IndexedDatabaseManager::InLowDiskSpaceMode()) { michael@0: rv = aDBFile->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!exists) { michael@0: NS_WARNING("Refusing to create database because disk space is low!"); michael@0: return NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr dbFileUrl = michael@0: IDBFactory::GetDatabaseFileURL(aDBFile, aPersistenceType, aGroup, aOrigin); michael@0: NS_ENSURE_TRUE(dbFileUrl, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr ss = michael@0: do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); michael@0: NS_ENSURE_TRUE(ss, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr connection; michael@0: rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection)); michael@0: if (rv == NS_ERROR_FILE_CORRUPTED) { michael@0: // If we're just opening the database during origin initialization, then michael@0: // we don't want to erase any files. The failure here will fail origin michael@0: // initialization too. michael@0: if (aName.IsVoid()) { michael@0: return rv; michael@0: } michael@0: michael@0: // Nuke the database file. The web services can recreate their data. michael@0: rv = aDBFile->Remove(false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = aFMDirectory->Exists(&exists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (exists) { michael@0: bool isDirectory; michael@0: rv = aFMDirectory->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: IDB_ENSURE_TRUE(isDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = aFMDirectory->Remove(true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: rv = ss->OpenDatabaseWithFileURL(dbFileUrl, getter_AddRefs(connection)); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = IDBFactory::SetDefaultPragmas(connection); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = connection->EnableModule(NS_LITERAL_CSTRING("filesystem")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Check to make sure that the database schema is correct. michael@0: int32_t schemaVersion; michael@0: rv = connection->GetSchemaVersion(&schemaVersion); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Unknown schema will fail origin initialization too michael@0: if (!schemaVersion && aName.IsVoid()) { michael@0: IDB_WARNING("Unable to open IndexedDB database, schema is not set!"); michael@0: return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; michael@0: } michael@0: michael@0: if (schemaVersion > kSQLiteSchemaVersion) { michael@0: IDB_WARNING("Unable to open IndexedDB database, schema is too high!"); michael@0: return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; michael@0: } michael@0: michael@0: bool vacuumNeeded = false; michael@0: michael@0: if (schemaVersion != kSQLiteSchemaVersion) { michael@0: #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) michael@0: if (!schemaVersion) { michael@0: // Have to do this before opening a transaction. michael@0: rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: // Turn on auto_vacuum mode to reclaim disk space on mobile devices. michael@0: "PRAGMA auto_vacuum = FULL; " michael@0: )); michael@0: if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) { michael@0: // mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE, michael@0: // which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR. michael@0: rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: #endif michael@0: michael@0: mozStorageTransaction transaction(connection, false, michael@0: mozIStorageConnection::TRANSACTION_IMMEDIATE); michael@0: michael@0: if (!schemaVersion) { michael@0: // Brand new file, initialize our tables. michael@0: rv = CreateTables(connection); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ASSERTION(NS_SUCCEEDED(connection->GetSchemaVersion(&schemaVersion)) && michael@0: schemaVersion == kSQLiteSchemaVersion, michael@0: "CreateTables set a bad schema version!"); michael@0: michael@0: nsCOMPtr stmt; michael@0: nsresult rv = connection->CreateStatement(NS_LITERAL_CSTRING( michael@0: "INSERT INTO database (name) " michael@0: "VALUES (:name)" michael@0: ), getter_AddRefs(stmt)); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = stmt->BindStringByName(NS_LITERAL_CSTRING("name"), aName); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = stmt->Execute(); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: } michael@0: else { michael@0: // This logic needs to change next time we change the schema! michael@0: static_assert(kSQLiteSchemaVersion == int32_t((14 << 4) + 0), michael@0: "Need upgrade code from schema version increase."); michael@0: michael@0: while (schemaVersion != kSQLiteSchemaVersion) { michael@0: if (schemaVersion == 4) { michael@0: rv = UpgradeSchemaFrom4To5(connection); michael@0: } michael@0: else if (schemaVersion == 5) { michael@0: rv = UpgradeSchemaFrom5To6(connection); michael@0: } michael@0: else if (schemaVersion == 6) { michael@0: rv = UpgradeSchemaFrom6To7(connection); michael@0: } michael@0: else if (schemaVersion == 7) { michael@0: rv = UpgradeSchemaFrom7To8(connection); michael@0: } michael@0: else if (schemaVersion == 8) { michael@0: rv = UpgradeSchemaFrom8To9_0(connection); michael@0: vacuumNeeded = true; michael@0: } michael@0: else if (schemaVersion == MakeSchemaVersion(9, 0)) { michael@0: rv = UpgradeSchemaFrom9_0To10_0(connection); michael@0: } michael@0: else if (schemaVersion == MakeSchemaVersion(10, 0)) { michael@0: rv = UpgradeSchemaFrom10_0To11_0(connection); michael@0: } michael@0: else if (schemaVersion == MakeSchemaVersion(11, 0)) { michael@0: rv = UpgradeSchemaFrom11_0To12_0(connection); michael@0: } michael@0: else if (schemaVersion == MakeSchemaVersion(12, 0)) { michael@0: rv = UpgradeSchemaFrom12_0To13_0(connection, &vacuumNeeded); michael@0: } michael@0: else if (schemaVersion == MakeSchemaVersion(13, 0)) { michael@0: rv = UpgradeSchemaFrom13_0To14_0(connection); michael@0: } michael@0: else { michael@0: NS_WARNING("Unable to open IndexedDB database, no upgrade path is " michael@0: "available!"); michael@0: IDB_REPORT_INTERNAL_ERR(); michael@0: return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = connection->GetSchemaVersion(&schemaVersion); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: NS_ASSERTION(schemaVersion == kSQLiteSchemaVersion, "Huh?!"); michael@0: } michael@0: michael@0: rv = transaction.Commit(); michael@0: if (rv == NS_ERROR_FILE_NO_DEVICE_SPACE) { michael@0: // mozstorage translates SQLITE_FULL to NS_ERROR_FILE_NO_DEVICE_SPACE, michael@0: // which we know better as NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR. michael@0: rv = NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (vacuumNeeded) { michael@0: rv = connection->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM;")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: connection.forget(aConnection); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: OpenDatabaseHelper::StartSetVersion() michael@0: { michael@0: NS_ASSERTION(mState == eSetVersionPending, "Why are we here?"); michael@0: michael@0: // In case we fail, fire error events michael@0: mState = eFiringEvents; michael@0: michael@0: nsresult rv = EnsureSuccessResult(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: Sequence storesToOpen; michael@0: nsRefPtr transaction = michael@0: IDBTransaction::Create(mDatabase, storesToOpen, michael@0: IDBTransaction::VERSION_CHANGE, true); michael@0: IDB_ENSURE_TRUE(transaction, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: nsRefPtr helper = michael@0: new SetVersionHelper(transaction, mOpenDBRequest, this, mRequestedVersion, michael@0: mCurrentVersion); michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never be null!"); michael@0: michael@0: rv = quotaManager->AcquireExclusiveAccess( michael@0: mDatabase, mDatabase->Origin(), michael@0: Nullable(mDatabase->Type()), helper, michael@0: &VersionChangeEventsRunnable::QueueVersionChange, michael@0: helper); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: // The SetVersionHelper is responsible for dispatching us back to the michael@0: // main thread again and changing the state to eSetVersionCompleted. michael@0: mState = eSetVersionPending; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: OpenDatabaseHelper::StartDelete() michael@0: { michael@0: NS_ASSERTION(mState == eDeletePending, "Why are we here?"); michael@0: michael@0: // In case we fail, fire error events michael@0: mState = eFiringEvents; michael@0: michael@0: nsresult rv = EnsureSuccessResult(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsRefPtr helper = michael@0: new DeleteDatabaseHelper(mOpenDBRequest, this, mCurrentVersion, mName, michael@0: mGroup, mASCIIOrigin, mPersistenceType); michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never be null!"); michael@0: michael@0: rv = quotaManager->AcquireExclusiveAccess( michael@0: mDatabase, mDatabase->Origin(), michael@0: Nullable(mDatabase->Type()), helper, michael@0: &VersionChangeEventsRunnable::QueueVersionChange, michael@0: helper); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: // The DeleteDatabaseHelper is responsible for dispatching us back to the michael@0: // main thread again and changing the state to eDeleteCompleted. michael@0: mState = eDeletePending; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: OpenDatabaseHelper::Run() michael@0: { michael@0: NS_ASSERTION(mState != eCreated, "Dispatch was not called?!?"); michael@0: michael@0: if (NS_IsMainThread()) { michael@0: PROFILER_MAIN_THREAD_LABEL("IndexedDB", "OpenDatabaseHelper::Run"); michael@0: michael@0: if (mState == eOpenPending) { michael@0: if (NS_FAILED(mResultCode)) { michael@0: return RunImmediately(); michael@0: } michael@0: michael@0: return DispatchToIOThread(); michael@0: } michael@0: michael@0: // If we need to queue up a SetVersionHelper, do that here. michael@0: if (mState == eSetVersionPending) { michael@0: nsresult rv = StartSetVersion(); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: SetError(rv); michael@0: // fall through and run the default error processing michael@0: } michael@0: else if (mState == eDeletePending) { michael@0: nsresult rv = StartDelete(); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: SetError(rv); michael@0: // fall through and run the default error processing michael@0: } michael@0: michael@0: // We've done whatever work we need to do on the DB thread, and any michael@0: // SetVersion/DeleteDatabase stuff is done by now. michael@0: NS_ASSERTION(mState == eFiringEvents || michael@0: mState == eSetVersionCompleted || michael@0: mState == eDeleteCompleted, "Why are we here?"); michael@0: michael@0: switch (mState) { michael@0: case eSetVersionCompleted: { michael@0: mState = eFiringEvents; michael@0: break; michael@0: } michael@0: michael@0: case eDeleteCompleted: { michael@0: // Destroy the database now (we should have the only ref). michael@0: mDatabase = nullptr; michael@0: michael@0: DatabaseInfo::Remove(mDatabaseId); michael@0: michael@0: mState = eFiringEvents; michael@0: break; michael@0: } michael@0: michael@0: case eFiringEvents: { michael@0: // Notify the request that we're done, but only if we didn't just michael@0: // finish a [SetVersion/DeleteDatabase]Helper. In that case, the michael@0: // helper tells the request that it is done, and we avoid calling michael@0: // NotifyHelperCompleted twice. michael@0: michael@0: nsresult rv = mOpenDBRequest->NotifyHelperCompleted(this); michael@0: if (NS_SUCCEEDED(mResultCode) && NS_FAILED(rv)) { michael@0: mResultCode = rv; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: default: michael@0: NS_NOTREACHED("Shouldn't get here!"); michael@0: } michael@0: michael@0: NS_ASSERTION(mState == eFiringEvents, "Why are we here?"); michael@0: michael@0: IDB_PROFILER_MARK("IndexedDB Request %llu: Running main thread " michael@0: "response (rv = %lu)", michael@0: "IDBRequest[%llu] MT Done", michael@0: mRequest->GetSerialNumber(), mResultCode); michael@0: michael@0: if (NS_FAILED(mResultCode)) { michael@0: DispatchErrorEvent(); michael@0: } else { michael@0: DispatchSuccessEvent(); michael@0: } michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never be null!"); michael@0: michael@0: quotaManager-> michael@0: AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mASCIIOrigin), michael@0: Nullable(mPersistenceType), michael@0: mDatabaseId); michael@0: michael@0: ReleaseMainThreadObjects(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: PROFILER_LABEL("IndexedDB", "OpenDatabaseHelper::Run"); michael@0: michael@0: // We're on the DB thread. michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: michael@0: IDB_PROFILER_MARK("IndexedDB Request %llu: Beginning database work", michael@0: "IDBRequest[%llu] DT Start", mRequest->GetSerialNumber()); michael@0: michael@0: NS_ASSERTION(mState == eDBWork, "Why are we here?"); michael@0: mResultCode = DoDatabaseWork(); michael@0: NS_ASSERTION(mState != eDBWork, "We should be doing something else now."); michael@0: michael@0: IDB_PROFILER_MARK("IndexedDB Request %llu: Finished database work (rv = %lu)", michael@0: "IDBRequest[%llu] DT Done", mRequest->GetSerialNumber(), michael@0: mResultCode); michael@0: michael@0: return NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL); michael@0: } michael@0: michael@0: nsresult michael@0: OpenDatabaseHelper::EnsureSuccessResult() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: PROFILER_MAIN_THREAD_LABEL("IndexedDB", michael@0: "OpenDatabaseHelper::EnsureSuccessResult"); michael@0: michael@0: nsRefPtr dbInfo; michael@0: if (DatabaseInfo::Get(mDatabaseId, getter_AddRefs(dbInfo))) { michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: NS_ASSERTION(dbInfo->name == mName && michael@0: dbInfo->version == mCurrentVersion && michael@0: dbInfo->persistenceType == mPersistenceType && michael@0: dbInfo->id == mDatabaseId && michael@0: dbInfo->filePath == mDatabaseFilePath, michael@0: "Metadata mismatch!"); michael@0: michael@0: uint32_t objectStoreCount = mObjectStores.Length(); michael@0: for (uint32_t index = 0; index < objectStoreCount; index++) { michael@0: nsRefPtr& info = mObjectStores[index]; michael@0: michael@0: ObjectStoreInfo* otherInfo = dbInfo->GetObjectStore(info->name); michael@0: NS_ASSERTION(otherInfo, "ObjectStore not known!"); michael@0: michael@0: NS_ASSERTION(info->name == otherInfo->name && michael@0: info->id == otherInfo->id && michael@0: info->keyPath == otherInfo->keyPath, michael@0: "Metadata mismatch!"); michael@0: NS_ASSERTION(dbInfo->ContainsStoreName(info->name), michael@0: "Object store names out of date!"); michael@0: NS_ASSERTION(info->indexes.Length() == otherInfo->indexes.Length(), michael@0: "Bad index length!"); michael@0: michael@0: uint32_t indexCount = info->indexes.Length(); michael@0: for (uint32_t indexIndex = 0; indexIndex < indexCount; indexIndex++) { michael@0: const IndexInfo& indexInfo = info->indexes[indexIndex]; michael@0: const IndexInfo& otherIndexInfo = otherInfo->indexes[indexIndex]; michael@0: NS_ASSERTION(indexInfo.id == otherIndexInfo.id, michael@0: "Bad index id!"); michael@0: NS_ASSERTION(indexInfo.name == otherIndexInfo.name, michael@0: "Bad index name!"); michael@0: NS_ASSERTION(indexInfo.keyPath == otherIndexInfo.keyPath, michael@0: "Bad index keyPath!"); michael@0: NS_ASSERTION(indexInfo.unique == otherIndexInfo.unique, michael@0: "Bad index unique value!"); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: } michael@0: else { michael@0: nsRefPtr newInfo(new DatabaseInfo()); michael@0: michael@0: newInfo->name = mName; michael@0: newInfo->group = mGroup; michael@0: newInfo->origin = mASCIIOrigin; michael@0: newInfo->persistenceType = mPersistenceType; michael@0: newInfo->id = mDatabaseId; michael@0: newInfo->filePath = mDatabaseFilePath; michael@0: michael@0: if (!DatabaseInfo::Put(newInfo)) { michael@0: return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; michael@0: } michael@0: michael@0: newInfo.swap(dbInfo); michael@0: michael@0: nsresult rv = IDBFactory::SetDatabaseMetadata(dbInfo, mCurrentVersion, michael@0: mObjectStores); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: NS_ASSERTION(mObjectStores.IsEmpty(), "Should have swapped!"); michael@0: } michael@0: michael@0: dbInfo->nextObjectStoreId = mLastObjectStoreId + 1; michael@0: dbInfo->nextIndexId = mLastIndexId + 1; michael@0: michael@0: nsRefPtr database = michael@0: IDBDatabase::Create(mOpenDBRequest, mOpenDBRequest->Factory(), michael@0: dbInfo.forget(), mASCIIOrigin, mFileManager, michael@0: mContentParent); michael@0: if (!database) { michael@0: IDB_REPORT_INTERNAL_ERR(); michael@0: return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR; michael@0: } michael@0: michael@0: NS_ASSERTION(!mDatabase, "Shouldn't have a database yet!"); michael@0: mDatabase.swap(database); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: OpenDatabaseHelper::GetSuccessResult(JSContext* aCx, michael@0: JS::MutableHandle aVal) michael@0: { michael@0: // Be careful not to load the database twice. michael@0: if (!mDatabase) { michael@0: nsresult rv = EnsureSuccessResult(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return WrapNative(aCx, NS_ISUPPORTS_CAST(EventTarget*, mDatabase), michael@0: aVal); michael@0: } michael@0: michael@0: nsresult michael@0: OpenDatabaseHelper::NotifySetVersionFinished() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread"); michael@0: NS_ASSERTION(mState = eSetVersionPending, "How did we get here?"); michael@0: michael@0: // Allow transaction creation to proceed. michael@0: mDatabase->ExitSetVersionTransaction(); michael@0: michael@0: mState = eSetVersionCompleted; michael@0: michael@0: // Dispatch ourself back to the main thread michael@0: return NS_DispatchToCurrentThread(this); michael@0: } michael@0: michael@0: nsresult michael@0: OpenDatabaseHelper::NotifyDeleteFinished() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread"); michael@0: NS_ASSERTION(mState == eDeletePending, "How did we get here?"); michael@0: michael@0: mState = eDeleteCompleted; michael@0: michael@0: // Dispatch ourself back to the main thread michael@0: return NS_DispatchToCurrentThread(this); michael@0: } michael@0: michael@0: void michael@0: OpenDatabaseHelper::BlockDatabase() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(mDatabase, "This is going bad fast."); michael@0: michael@0: mDatabase->EnterSetVersionTransaction(); michael@0: } michael@0: michael@0: void michael@0: OpenDatabaseHelper::DispatchSuccessEvent() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: PROFILER_MAIN_THREAD_LABEL("IndexedDB", michael@0: "OpenDatabaseHelper::DispatchSuccessEvent"); michael@0: michael@0: nsRefPtr event = michael@0: CreateGenericEvent(mOpenDBRequest, NS_LITERAL_STRING(SUCCESS_EVT_STR), michael@0: eDoesNotBubble, eNotCancelable); michael@0: if (!event) { michael@0: NS_ERROR("Failed to create event!"); michael@0: return; michael@0: } michael@0: michael@0: bool dummy; michael@0: mOpenDBRequest->DispatchEvent(event, &dummy); michael@0: } michael@0: michael@0: void michael@0: OpenDatabaseHelper::DispatchErrorEvent() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: PROFILER_MAIN_THREAD_LABEL("IndexedDB", michael@0: "OpenDatabaseHelper::DispatchErrorEvent"); michael@0: michael@0: nsRefPtr event = michael@0: CreateGenericEvent(mOpenDBRequest, NS_LITERAL_STRING(ERROR_EVT_STR), michael@0: eDoesBubble, eCancelable); michael@0: if (!event) { michael@0: NS_ERROR("Failed to create event!"); michael@0: return; michael@0: } michael@0: michael@0: ErrorResult rv; michael@0: nsRefPtr error = mOpenDBRequest->GetError(rv); michael@0: michael@0: NS_ASSERTION(!rv.Failed(), "This shouldn't be failing at this point!"); michael@0: if (!error) { michael@0: mOpenDBRequest->SetError(mResultCode); michael@0: } michael@0: michael@0: bool dummy; michael@0: mOpenDBRequest->DispatchEvent(event, &dummy); michael@0: } michael@0: michael@0: void michael@0: OpenDatabaseHelper::ReleaseMainThreadObjects() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: michael@0: mOpenDBRequest = nullptr; michael@0: mDatabase = nullptr; michael@0: michael@0: HelperBase::ReleaseMainThreadObjects(); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(SetVersionHelper, AsyncConnectionHelper) michael@0: michael@0: nsresult michael@0: SetVersionHelper::Init() michael@0: { michael@0: // Block transaction creation until we are done. michael@0: mOpenHelper->BlockDatabase(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: SetVersionHelper::DoDatabaseWork(mozIStorageConnection* aConnection) michael@0: { michael@0: NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: NS_ASSERTION(aConnection, "Passing a null connection!"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "SetVersionHelper::DoDatabaseWork"); michael@0: michael@0: nsCOMPtr stmt; michael@0: nsresult rv = aConnection->CreateStatement(NS_LITERAL_CSTRING( michael@0: "UPDATE database " michael@0: "SET version = :version" michael@0: ), getter_AddRefs(stmt)); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("version"), michael@0: mRequestedVersion); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: if (NS_FAILED(stmt->Execute())) { michael@0: return NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: SetVersionHelper::GetSuccessResult(JSContext* aCx, michael@0: JS::MutableHandle aVal) michael@0: { michael@0: DatabaseInfo* info = mDatabase->Info(); michael@0: info->version = mRequestedVersion; michael@0: michael@0: NS_ASSERTION(mTransaction, "Better have a transaction!"); michael@0: michael@0: mOpenRequest->SetTransaction(mTransaction); michael@0: michael@0: return WrapNative(aCx, NS_ISUPPORTS_CAST(EventTarget*, mDatabase), michael@0: aVal); michael@0: } michael@0: michael@0: nsresult michael@0: SetVersionHelper::OnExclusiveAccessAcquired() michael@0: { michael@0: nsresult rv = DispatchToTransactionPool(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // static michael@0: template michael@0: void michael@0: VersionChangeEventsRunnable::QueueVersionChange( michael@0: nsTArray >& aDatabases, michael@0: void* aClosure) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(!aDatabases.IsEmpty(), "Why are we here?"); michael@0: michael@0: T* closure = static_cast(aClosure); michael@0: michael@0: nsRefPtr eventsRunnable = michael@0: new VersionChangeEventsRunnable(closure->mOpenHelper->Database(), michael@0: closure->mOpenRequest, michael@0: aDatabases, michael@0: closure->mCurrentVersion, michael@0: closure->RequestedVersion()); michael@0: michael@0: NS_DispatchToCurrentThread(eventsRunnable); michael@0: } michael@0: michael@0: already_AddRefed michael@0: SetVersionHelper::CreateSuccessEvent(mozilla::dom::EventTarget* aOwner) michael@0: { michael@0: NS_ASSERTION(mCurrentVersion < mRequestedVersion, "Huh?"); michael@0: michael@0: return IDBVersionChangeEvent::CreateUpgradeNeeded(aOwner, michael@0: mCurrentVersion, michael@0: mRequestedVersion); michael@0: } michael@0: michael@0: nsresult michael@0: SetVersionHelper::NotifyTransactionPreComplete(IDBTransaction* aTransaction) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aTransaction, "This is unexpected."); michael@0: NS_ASSERTION(mOpenRequest, "Why don't we have a request?"); michael@0: michael@0: return mOpenHelper->NotifySetVersionFinished(); michael@0: } michael@0: michael@0: nsresult michael@0: SetVersionHelper::NotifyTransactionPostComplete(IDBTransaction* aTransaction) michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Wrong thread!"); michael@0: NS_ASSERTION(aTransaction, "This is unexpected."); michael@0: NS_ASSERTION(mOpenRequest, "Why don't we have a request?"); michael@0: michael@0: // If we hit an error, the OpenDatabaseHelper needs to get that error too. michael@0: nsresult rv = GetResultCode(); michael@0: if (NS_FAILED(rv)) { michael@0: mOpenHelper->SetError(rv); michael@0: } michael@0: michael@0: // If the transaction was aborted, we should throw an error message. michael@0: if (aTransaction->IsAborted()) { michael@0: mOpenHelper->SetError(aTransaction->GetAbortCode()); michael@0: } michael@0: michael@0: mOpenRequest->SetTransaction(nullptr); michael@0: mOpenRequest = nullptr; michael@0: michael@0: mOpenHelper = nullptr; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(DeleteDatabaseHelper, AsyncConnectionHelper); michael@0: michael@0: nsresult michael@0: DeleteDatabaseHelper::DoDatabaseWork(mozIStorageConnection* aConnection) michael@0: { michael@0: AssertIsOnIOThread(); michael@0: NS_ASSERTION(IndexedDatabaseManager::IsMainProcess(), "Wrong process!"); michael@0: NS_ASSERTION(!aConnection, "How did we get a connection here?"); michael@0: michael@0: PROFILER_LABEL("IndexedDB", "DeleteDatabaseHelper::DoDatabaseWork"); michael@0: michael@0: const StoragePrivilege& privilege = mOpenHelper->Privilege(); michael@0: michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "This should never fail!"); michael@0: michael@0: nsCOMPtr directory; michael@0: nsresult rv = quotaManager->GetDirectoryForOrigin(mPersistenceType, michael@0: mASCIIOrigin, michael@0: getter_AddRefs(directory)); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: NS_ASSERTION(directory, "What?"); michael@0: michael@0: rv = directory->Append(NS_LITERAL_STRING(IDB_DIRECTORY_NAME)); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: nsAutoString filename; michael@0: rv = GetDatabaseFilename(mName, filename); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: nsCOMPtr dbFile; michael@0: rv = directory->Clone(getter_AddRefs(dbFile)); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = dbFile->Append(filename + NS_LITERAL_STRING(".sqlite")); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: bool exists = false; michael@0: rv = dbFile->Exists(&exists); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: if (exists) { michael@0: int64_t fileSize; michael@0: michael@0: if (privilege != Chrome) { michael@0: rv = dbFile->GetFileSize(&fileSize); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: } michael@0: michael@0: rv = dbFile->Remove(false); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: if (privilege != Chrome) { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Shouldn't be null!"); michael@0: michael@0: quotaManager->DecreaseUsageForOrigin(mPersistenceType, mGroup, michael@0: mASCIIOrigin, fileSize); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr dbJournalFile; michael@0: rv = directory->Clone(getter_AddRefs(dbJournalFile)); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = dbJournalFile->Append(filename + NS_LITERAL_STRING(".sqlite-journal")); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = dbJournalFile->Exists(&exists); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: if (exists) { michael@0: rv = dbJournalFile->Remove(false); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: } michael@0: michael@0: nsCOMPtr fmDirectory; michael@0: rv = directory->Clone(getter_AddRefs(fmDirectory)); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = fmDirectory->Append(filename); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: rv = fmDirectory->Exists(&exists); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: if (exists) { michael@0: bool isDirectory; michael@0: rv = fmDirectory->IsDirectory(&isDirectory); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: IDB_ENSURE_TRUE(isDirectory, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: uint64_t usage = 0; michael@0: michael@0: if (privilege != Chrome) { michael@0: rv = FileManager::GetUsage(fmDirectory, &usage); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: } michael@0: michael@0: rv = fmDirectory->Remove(true); michael@0: IDB_ENSURE_SUCCESS(rv, NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR); michael@0: michael@0: if (privilege != Chrome) { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "Shouldn't be null!"); michael@0: michael@0: quotaManager->DecreaseUsageForOrigin(mPersistenceType, mGroup, michael@0: mASCIIOrigin, usage); michael@0: } michael@0: } michael@0: michael@0: IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get(); michael@0: NS_ASSERTION(mgr, "This should never fail!"); michael@0: michael@0: mgr->InvalidateFileManager(mPersistenceType, mASCIIOrigin, mName); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: DeleteDatabaseHelper::GetSuccessResult(JSContext* aCx, JS::MutableHandle aVal) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: DeleteDatabaseHelper::OnExclusiveAccessAcquired() michael@0: { michael@0: QuotaManager* quotaManager = QuotaManager::Get(); michael@0: NS_ASSERTION(quotaManager, "We should definitely have a manager here"); michael@0: michael@0: nsresult rv = Dispatch(quotaManager->IOThread()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: DeleteDatabaseHelper::Init() michael@0: { michael@0: // Note that there's no need to block the database here, since the page michael@0: // never gets to touch it, and all other databases must be closed. michael@0: michael@0: return NS_OK; michael@0: }