diff options
Diffstat (limited to 'dom/fs/parent/datamodel/SchemaVersion002.cpp')
-rw-r--r-- | dom/fs/parent/datamodel/SchemaVersion002.cpp | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/dom/fs/parent/datamodel/SchemaVersion002.cpp b/dom/fs/parent/datamodel/SchemaVersion002.cpp new file mode 100644 index 0000000000..57d4736b88 --- /dev/null +++ b/dom/fs/parent/datamodel/SchemaVersion002.cpp @@ -0,0 +1,618 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "SchemaVersion002.h" + +#include "FileSystemFileManager.h" +#include "FileSystemHashSource.h" +#include "FileSystemHashStorageFunction.h" +#include "ResultStatement.h" +#include "StartedTransaction.h" +#include "fs/FileSystemConstants.h" +#include "mozStorageHelper.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "nsID.h" + +namespace mozilla::dom::fs { + +namespace { + +nsresult CreateFileIds(ResultConnection& aConn) { + return aConn->ExecuteSimpleSQL( + "CREATE TABLE IF NOT EXISTS FileIds ( " + "fileId BLOB PRIMARY KEY, " + "handle BLOB, " + "FOREIGN KEY (handle) " + "REFERENCES Files (handle) " + "ON DELETE SET NULL ) " + ";"_ns); +} + +nsresult CreateMainFiles(ResultConnection& aConn) { + return aConn->ExecuteSimpleSQL( + "CREATE TABLE IF NOT EXISTS MainFiles ( " + "handle BLOB UNIQUE, " + "fileId BLOB UNIQUE, " + "FOREIGN KEY (handle) REFERENCES Files (handle) " + "ON DELETE CASCADE, " + "FOREIGN KEY (fileId) REFERENCES FileIds (fileId) " + "ON DELETE SET NULL ) " + ";"_ns); +} + +nsresult PopulateFileIds(ResultConnection& aConn) { + return aConn->ExecuteSimpleSQL( + "INSERT OR IGNORE INTO FileIds ( fileId, handle ) " + "SELECT handle, handle FROM Files " + ";"_ns); +} + +nsresult PopulateMainFiles(ResultConnection& aConn) { + return aConn->ExecuteSimpleSQL( + "INSERT OR IGNORE INTO MainFiles ( fileId, handle ) " + "SELECT handle, handle FROM Files " + ";"_ns); +} + +Result<Ok, QMResult> ClearInvalidFileIds( + ResultConnection& aConn, data::FileSystemFileManager& aFileManager) { + // We cant't just clear all file ids because if a file was accessed using + // writable file stream a new file id was created which is not the same as + // entry id. + + // Get all file ids first. + QM_TRY_INSPECT( + const auto& allFileIds, + ([&aConn]() -> Result<nsTArray<FileId>, QMResult> { + const nsLiteralCString allFileIdsQuery = + "SELECT fileId FROM FileIds;"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConn, allFileIdsQuery)); + + nsTArray<FileId> fileIds; + + while (true) { + QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); + if (!moreResults) { + break; + } + + QM_TRY_UNWRAP(FileId fileId, stmt.GetFileIdByColumn(/* Column */ 0u)); + + fileIds.AppendElement(fileId); + } + + return std::move(fileIds); + }())); + + // Filter out file ids which have non-zero-sized files on disk. + QM_TRY_INSPECT(const auto& invalidFileIds, + ([&aFileManager](const nsTArray<FileId>& aFileIds) + -> Result<nsTArray<FileId>, QMResult> { + nsTArray<FileId> fileIds; + + for (const auto& fileId : aFileIds) { + QM_TRY_UNWRAP(auto file, aFileManager.GetFile(fileId)); + + QM_TRY_INSPECT(const bool& exists, + QM_TO_RESULT_INVOKE_MEMBER(file, Exists)); + + if (exists) { + QM_TRY_INSPECT( + const int64_t& fileSize, + QM_TO_RESULT_INVOKE_MEMBER(file, GetFileSize)); + + if (fileSize != 0) { + continue; + } + + QM_TRY(QM_TO_RESULT(file->Remove(false))); + } + + fileIds.AppendElement(fileId); + } + + return std::move(fileIds); + }(allFileIds))); + + // Finally, clear invalid file ids. + QM_TRY(([&aConn](const nsTArray<FileId>& aFileIds) -> Result<Ok, QMResult> { + for (const auto& fileId : aFileIds) { + const nsLiteralCString clearFileIdsQuery = + "DELETE FROM FileIds " + "WHERE fileId = :fileId " + ";"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConn, clearFileIdsQuery)); + + QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, fileId))); + + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + return Ok{}; + }(invalidFileIds))); + + return Ok{}; +} + +Result<Ok, QMResult> ClearInvalidMainFiles( + ResultConnection& aConn, data::FileSystemFileManager& aFileManager) { + // We cant't just clear all main files because if a file was accessed using + // writable file stream a new main file was created which is not the same as + // entry id. + + // Get all main files first. + QM_TRY_INSPECT( + const auto& allMainFiles, + ([&aConn]() -> Result<nsTArray<std::pair<EntryId, FileId>>, QMResult> { + const nsLiteralCString allMainFilesQuery = + "SELECT handle, fileId FROM MainFiles;"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConn, allMainFilesQuery)); + + nsTArray<std::pair<EntryId, FileId>> mainFiles; + + while (true) { + QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); + if (!moreResults) { + break; + } + + QM_TRY_UNWRAP(EntryId entryId, + stmt.GetEntryIdByColumn(/* Column */ 0u)); + QM_TRY_UNWRAP(FileId fileId, stmt.GetFileIdByColumn(/* Column */ 1u)); + + mainFiles.AppendElement(std::pair<EntryId, FileId>(entryId, fileId)); + } + + return std::move(mainFiles); + }())); + + // Filter out main files which have non-zero-sized files on disk. + QM_TRY_INSPECT( + const auto& invalidMainFiles, + ([&aFileManager](const nsTArray<std::pair<EntryId, FileId>>& aMainFiles) + -> Result<nsTArray<std::pair<EntryId, FileId>>, QMResult> { + nsTArray<std::pair<EntryId, FileId>> mainFiles; + + for (const auto& mainFile : aMainFiles) { + QM_TRY_UNWRAP(auto file, aFileManager.GetFile(mainFile.second)); + + QM_TRY_INSPECT(const bool& exists, + QM_TO_RESULT_INVOKE_MEMBER(file, Exists)); + + if (exists) { + QM_TRY_INSPECT(const int64_t& fileSize, + QM_TO_RESULT_INVOKE_MEMBER(file, GetFileSize)); + + if (fileSize != 0) { + continue; + } + + QM_TRY(QM_TO_RESULT(file->Remove(false))); + } + + mainFiles.AppendElement(mainFile); + } + + return std::move(mainFiles); + }(allMainFiles))); + + // Finally, clear invalid main files. + QM_TRY(([&aConn](const nsTArray<std::pair<EntryId, FileId>>& aMainFiles) + -> Result<Ok, QMResult> { + for (const auto& mainFile : aMainFiles) { + const nsLiteralCString clearMainFilesQuery = + "DELETE FROM MainFiles " + "WHERE handle = :entryId AND fileId = :fileId " + ";"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConn, clearMainFilesQuery)); + + QM_TRY( + QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, mainFile.first))); + QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, mainFile.second))); + + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + return Ok{}; + }(invalidMainFiles))); + + return Ok{}; +} + +nsresult ConnectUsagesToFileIds(ResultConnection& aConn) { + QM_TRY( + MOZ_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns))); + + auto turnForeignKeysBackOn = MakeScopeExit([&aConn]() { + QM_WARNONLY_TRY( + MOZ_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns))); + }); + + QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn)); + + QM_TRY(MOZ_TO_RESULT( + aConn->ExecuteSimpleSQL("DROP TABLE IF EXISTS migrateUsages ;"_ns))); + + QM_TRY(MOZ_TO_RESULT(aConn->ExecuteSimpleSQL( + "CREATE TABLE migrateUsages ( " + "handle BLOB PRIMARY KEY, " + "usage INTEGER NOT NULL DEFAULT 0, " + "tracked BOOLEAN NOT NULL DEFAULT 0 CHECK (tracked IN (0, 1)), " + "CONSTRAINT handles_are_fileIds " + "FOREIGN KEY (handle) " + "REFERENCES FileIds (fileId) " + "ON DELETE CASCADE ) " + ";"_ns))); + + QM_TRY(MOZ_TO_RESULT(aConn->ExecuteSimpleSQL( + "INSERT INTO migrateUsages ( handle, usage, tracked ) " + "SELECT handle, usage, tracked FROM Usages ;"_ns))); + + QM_TRY(MOZ_TO_RESULT(aConn->ExecuteSimpleSQL("DROP TABLE Usages;"_ns))); + + QM_TRY(MOZ_TO_RESULT(aConn->ExecuteSimpleSQL( + "ALTER TABLE migrateUsages RENAME TO Usages;"_ns))); + + QM_TRY( + MOZ_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_key_check;"_ns))); + + QM_TRY(MOZ_TO_RESULT(transaction.Commit())); + + return NS_OK; +} + +nsresult CreateEntryNamesView(ResultConnection& aConn) { + return aConn->ExecuteSimpleSQL( + "CREATE VIEW IF NOT EXISTS EntryNames AS " + "SELECT isFile, handle, parent, name FROM Entries INNER JOIN ( " + "SELECT 1 AS isFile, handle, name FROM Files UNION " + "SELECT 0, handle, name FROM Directories ) " + "USING (handle) " + ";"_ns); +} + +nsresult FixEntryIds(const ResultConnection& aConnection, + const EntryId& aRootEntry) { + const nsLiteralCString calculateHashesQuery = + "CREATE TEMPORARY TABLE EntryMigrationTable AS " + "WITH RECURSIVE " + "rehashMap( depth, isFile, handle, parent, name, hash ) AS ( " + "SELECT 0, isFile, handle, parent, name, hashEntry( :rootEntry, name ) " + "FROM EntryNames WHERE parent = :rootEntry UNION SELECT " + "1 + depth, EntryNames.isFile, EntryNames.handle, EntryNames.parent, " + "EntryNames.name, hashEntry( rehashMap.hash, EntryNames.name ) " + "FROM rehashMap, EntryNames WHERE rehashMap.handle = EntryNames.parent ) " + "SELECT depth, isFile, handle, parent, name, hash FROM rehashMap " + ";"_ns; + + const nsLiteralCString createIndexByDepthQuery = + "CREATE INDEX indexOnDepth ON EntryMigrationTable ( depth ); "_ns; + + // To avoid constraint violation, new entries are inserted under a temporary + // parent. + + const nsLiteralCString insertTemporaryParentEntry = + "INSERT INTO Entries ( handle, parent ) " + "VALUES ( :tempParent, :rootEntry ) ;"_ns; + + const nsLiteralCString flagTemporaryParentAsDir = + "INSERT INTO Directories ( handle, name ) " + "VALUES ( :tempParent, 'temp' ) ;"_ns; + + const nsLiteralCString insertNewEntriesQuery = + "INSERT INTO Entries ( handle, parent ) " + "SELECT hash, :tempParent FROM EntryMigrationTable WHERE hash != handle " + ";"_ns; + + const nsLiteralCString insertNewDirectoriesQuery = + "INSERT INTO Directories ( handle, name ) " + "SELECT hash, name FROM EntryMigrationTable " + "WHERE isFile = 0 AND hash != handle " + "ORDER BY depth " + ";"_ns; + + const nsLiteralCString insertNewFilesQuery = + "INSERT INTO Files ( handle, type, name ) " + "SELECT EntryMigrationTable.hash, Files.type, EntryMigrationTable.name " + "FROM EntryMigrationTable INNER JOIN Files USING (handle) " + "WHERE EntryMigrationTable.isFile = 1 AND hash != handle " + ";"_ns; + + const nsLiteralCString updateFileMappingsQuery = + "UPDATE FileIds SET handle = hash " + "FROM ( SELECT handle, hash FROM EntryMigrationTable WHERE hash != " + "handle ) " + "AS replacement WHERE FileIds.handle = replacement.handle " + ";"_ns; + + const nsLiteralCString updateMainFilesQuery = + "UPDATE MainFiles SET handle = hash " + "FROM ( SELECT handle, hash FROM EntryMigrationTable WHERE hash != " + "handle ) " + "AS replacement WHERE MainFiles.handle = replacement.handle " + ";"_ns; + + // Now fix the parents. + const nsLiteralCString updateEntryMappingsQuery = + "UPDATE Entries SET parent = hash " + "FROM ( SELECT Lhs.hash AS handle, Rhs.hash AS hash, Lhs.depth AS depth " + "FROM EntryMigrationTable AS Lhs " + "INNER JOIN EntryMigrationTable AS Rhs " + "ON Rhs.handle = Lhs.parent ORDER BY depth ) AS replacement " + "WHERE Entries.handle = replacement.handle " + "AND Entries.parent = :tempParent " + ";"_ns; + + const nsLiteralCString cleanupOldEntriesQuery = + "DELETE FROM Entries WHERE handle IN " + "( SELECT handle FROM EntryMigrationTable WHERE hash != handle ) " + ";"_ns; + + const nsLiteralCString cleanupTemporaryParent = + "DELETE FROM Entries WHERE handle = :tempParent ;"_ns; + + const nsLiteralCString dropIndexByDepthQuery = + "DROP INDEX indexOnDepth ; "_ns; + + // Index is automatically deleted + const nsLiteralCString cleanupTemporaries = + "DROP TABLE EntryMigrationTable ;"_ns; + + EntryId tempParent(nsCString(nsID::GenerateUUID().ToString().get())); + + nsCOMPtr<mozIStorageFunction> rehashFunction = + new data::FileSystemHashStorageFunction(); + QM_TRY(MOZ_TO_RESULT(aConnection->CreateFunction("hashEntry"_ns, + /* number of arguments */ 2, + rehashFunction))); + auto finallyRemoveFunction = MakeScopeExit([&aConnection]() { + QM_WARNONLY_TRY(MOZ_TO_RESULT(aConnection->RemoveFunction("hashEntry"_ns))); + }); + + // We need this to make sure the old entries get removed + QM_TRY(MOZ_TO_RESULT( + aConnection->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns))); + + QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConnection)); + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, calculateHashesQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("rootEntry"_ns, aRootEntry))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + QM_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL(createIndexByDepthQuery))); + + { + QM_TRY_UNWRAP( + ResultStatement stmt, + ResultStatement::Create(aConnection, insertTemporaryParentEntry)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent))); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("rootEntry"_ns, aRootEntry))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP( + ResultStatement stmt, + ResultStatement::Create(aConnection, flagTemporaryParentAsDir)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, insertNewEntriesQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP( + ResultStatement stmt, + ResultStatement::Create(aConnection, insertNewDirectoriesQuery)); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, insertNewFilesQuery)); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP( + ResultStatement stmt, + ResultStatement::Create(aConnection, updateFileMappingsQuery)); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, updateMainFilesQuery)); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP( + ResultStatement stmt, + ResultStatement::Create(aConnection, updateEntryMappingsQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, cleanupOldEntriesQuery)); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, cleanupTemporaryParent)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + QM_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL(dropIndexByDepthQuery))); + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, cleanupTemporaries)); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + QM_TRY(QM_TO_RESULT(transaction.Commit())); + + QM_WARNONLY_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL("VACUUM;"_ns))); + + return NS_OK; +} + +} // namespace + +Result<DatabaseVersion, QMResult> SchemaVersion002::InitializeConnection( + ResultConnection& aConn, data::FileSystemFileManager& aFileManager, + const Origin& aOrigin) { + QM_TRY_UNWRAP(const bool wasEmpty, CheckIfEmpty(aConn)); + + DatabaseVersion currentVersion = 0; + + if (wasEmpty) { + QM_TRY(QM_TO_RESULT(SetEncoding(aConn))); + } else { + QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(¤tVersion))); + } + + if (currentVersion < sVersion) { + MOZ_ASSERT_IF(0 != currentVersion, 1 == currentVersion); + + QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn)); + + if (0 == currentVersion) { + QM_TRY(QM_TO_RESULT(SchemaVersion001::CreateTables(aConn, aOrigin))); + } + + QM_TRY(QM_TO_RESULT(CreateFileIds(aConn))); + + if (!wasEmpty) { + QM_TRY(QM_TO_RESULT(PopulateFileIds(aConn))); + } + + QM_TRY(QM_TO_RESULT(ConnectUsagesToFileIds(aConn))); + + QM_TRY(QM_TO_RESULT(CreateMainFiles(aConn))); + if (!wasEmpty) { + QM_TRY(QM_TO_RESULT(PopulateMainFiles(aConn))); + } + + QM_TRY(QM_TO_RESULT(CreateEntryNamesView(aConn))); + + QM_TRY(QM_TO_RESULT(aConn->SetSchemaVersion(sVersion))); + + QM_TRY(QM_TO_RESULT(transaction.Commit())); + + if (!wasEmpty) { + QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL("VACUUM;"_ns))); + } + } + + // The upgrade from version 1 to version 2 was buggy, so we have to check if + // the Usages table still references the Files table which is a sign that + // the upgrade wasn't complete. This extra query has only negligible perf + // impact. See bug 1847989. + auto UsagesTableRefsFilesTable = [&aConn]() -> Result<bool, QMResult> { + const nsLiteralCString query = + "SELECT pragma_foreign_key_list.'table'=='Files' " + "FROM pragma_foreign_key_list('Usages');"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, ResultStatement::Create(aConn, query)); + + return stmt.YesOrNoQuery(); + }; + + QM_TRY_UNWRAP(auto usagesTableRefsFilesTable, UsagesTableRefsFilesTable()); + + if (usagesTableRefsFilesTable) { + QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn)); + + // The buggy upgrade didn't call PopulateFileIds, ConnectUsagesToFileIds + // and PopulateMainFiles was completely missing. Since invalid file ids + // and main files could be inserted when the profile was broken, we need + // to clear them before populating. + QM_TRY(ClearInvalidFileIds(aConn, aFileManager)); + QM_TRY(QM_TO_RESULT(PopulateFileIds(aConn))); + QM_TRY(QM_TO_RESULT(ConnectUsagesToFileIds(aConn))); + QM_TRY(ClearInvalidMainFiles(aConn, aFileManager)); + QM_TRY(QM_TO_RESULT(PopulateMainFiles(aConn))); + + QM_TRY(QM_TO_RESULT(transaction.Commit())); + + QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL("VACUUM;"_ns))); + + QM_TRY_UNWRAP(usagesTableRefsFilesTable, UsagesTableRefsFilesTable()); + MOZ_ASSERT(!usagesTableRefsFilesTable); + } + + // In schema version 001, entryId was unique but not necessarily related to + // a path. For schema 002, we have to fix all entryIds to be derived from + // the underlying path. + auto OneTimeRehashingDone = [&aConn]() -> Result<bool, QMResult> { + const nsLiteralCString query = + "SELECT EXISTS (SELECT 1 FROM sqlite_master " + "WHERE type='table' AND name='RehashedFrom001to002' ) ;"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, ResultStatement::Create(aConn, query)); + + return stmt.YesOrNoQuery(); + }; + + QM_TRY_UNWRAP(auto oneTimeRehashingDone, OneTimeRehashingDone()); + + if (!oneTimeRehashingDone) { + const nsLiteralCString findRootEntry = + "SELECT handle FROM Entries WHERE parent IS NULL ;"_ns; + + EntryId rootId; + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConn, findRootEntry)); + + QM_TRY_UNWRAP(DebugOnly<bool> moreResults, stmt.ExecuteStep()); + MOZ_ASSERT(moreResults); + + QM_TRY_UNWRAP(rootId, stmt.GetEntryIdByColumn(/* Column */ 0u)); + } + + MOZ_ASSERT(!rootId.IsEmpty()); + + QM_TRY(QM_TO_RESULT(FixEntryIds(aConn, rootId))); + + QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL( + "CREATE TABLE RehashedFrom001to002 (id INTEGER PRIMARY KEY);"_ns))); + + QM_TRY_UNWRAP(DebugOnly<bool> isDoneNow, OneTimeRehashingDone()); + MOZ_ASSERT(isDoneNow); + } + + QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns))); + + QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(¤tVersion))); + + return currentVersion; +} + +} // namespace mozilla::dom::fs |