tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SchemaVersion002.cpp (20951B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "SchemaVersion002.h"
      8 
      9 #include "FileSystemFileManager.h"
     10 #include "FileSystemHashSource.h"
     11 #include "FileSystemHashStorageFunction.h"
     12 #include "ResultStatement.h"
     13 #include "StartedTransaction.h"
     14 #include "fs/FileSystemConstants.h"
     15 #include "mozStorageHelper.h"
     16 #include "mozilla/dom/quota/QuotaCommon.h"
     17 #include "mozilla/dom/quota/ResultExtensions.h"
     18 #include "nsID.h"
     19 
     20 namespace mozilla::dom::fs {
     21 
     22 namespace {
     23 
     24 nsresult CreateFileIds(ResultConnection& aConn) {
     25  return aConn->ExecuteSimpleSQL(
     26      "CREATE TABLE IF NOT EXISTS FileIds ( "
     27      "fileId BLOB PRIMARY KEY, "
     28      "handle BLOB, "
     29      "FOREIGN KEY (handle) "
     30      "REFERENCES Files (handle) "
     31      "ON DELETE SET NULL ) "
     32      ";"_ns);
     33 }
     34 
     35 nsresult CreateMainFiles(ResultConnection& aConn) {
     36  return aConn->ExecuteSimpleSQL(
     37      "CREATE TABLE IF NOT EXISTS MainFiles ( "
     38      "handle BLOB UNIQUE, "
     39      "fileId BLOB UNIQUE, "
     40      "FOREIGN KEY (handle) REFERENCES Files (handle) "
     41      "ON DELETE CASCADE, "
     42      "FOREIGN KEY (fileId) REFERENCES FileIds (fileId) "
     43      "ON DELETE SET NULL ) "
     44      ";"_ns);
     45 }
     46 
     47 nsresult PopulateFileIds(ResultConnection& aConn) {
     48  return aConn->ExecuteSimpleSQL(
     49      "INSERT OR IGNORE INTO FileIds ( fileId, handle ) "
     50      "SELECT handle, handle FROM Files "
     51      ";"_ns);
     52 }
     53 
     54 nsresult PopulateMainFiles(ResultConnection& aConn) {
     55  return aConn->ExecuteSimpleSQL(
     56      "INSERT OR IGNORE INTO MainFiles ( fileId, handle ) "
     57      "SELECT handle, handle FROM Files "
     58      ";"_ns);
     59 }
     60 
     61 Result<Ok, QMResult> ClearInvalidFileIds(
     62    ResultConnection& aConn, data::FileSystemFileManager& aFileManager) {
     63  // We cant't just clear all file ids because if a file was accessed using
     64  // writable file stream a new file id was created which is not the same as
     65  // entry id.
     66 
     67  // Get all file ids first.
     68  QM_TRY_INSPECT(
     69      const auto& allFileIds,
     70      ([&aConn]() -> Result<nsTArray<FileId>, QMResult> {
     71        const nsLiteralCString allFileIdsQuery =
     72            "SELECT fileId FROM FileIds;"_ns;
     73 
     74        QM_TRY_UNWRAP(ResultStatement stmt,
     75                      ResultStatement::Create(aConn, allFileIdsQuery));
     76 
     77        nsTArray<FileId> fileIds;
     78 
     79        while (true) {
     80          QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
     81          if (!moreResults) {
     82            break;
     83          }
     84 
     85          QM_TRY_UNWRAP(FileId fileId, stmt.GetFileIdByColumn(/* Column */ 0u));
     86 
     87          fileIds.AppendElement(fileId);
     88        }
     89 
     90        return std::move(fileIds);
     91      }()));
     92 
     93  // Filter out file ids which have non-zero-sized files on disk.
     94  QM_TRY_INSPECT(const auto& invalidFileIds,
     95                 ([&aFileManager](const nsTArray<FileId>& aFileIds)
     96                      -> Result<nsTArray<FileId>, QMResult> {
     97                   nsTArray<FileId> fileIds;
     98 
     99                   for (const auto& fileId : aFileIds) {
    100                     QM_TRY_UNWRAP(auto file, aFileManager.GetFile(fileId));
    101 
    102                     QM_TRY_INSPECT(const bool& exists,
    103                                    QM_TO_RESULT_INVOKE_MEMBER(file, Exists));
    104 
    105                     if (exists) {
    106                       QM_TRY_INSPECT(
    107                           const int64_t& fileSize,
    108                           QM_TO_RESULT_INVOKE_MEMBER(file, GetFileSize));
    109 
    110                       if (fileSize != 0) {
    111                         continue;
    112                       }
    113 
    114                       QM_TRY(QM_TO_RESULT(file->Remove(false)));
    115                     }
    116 
    117                     fileIds.AppendElement(fileId);
    118                   }
    119 
    120                   return std::move(fileIds);
    121                 }(allFileIds)));
    122 
    123  // Finally, clear invalid file ids.
    124  QM_TRY(([&aConn](const nsTArray<FileId>& aFileIds) -> Result<Ok, QMResult> {
    125    for (const auto& fileId : aFileIds) {
    126      const nsLiteralCString clearFileIdsQuery =
    127          "DELETE FROM FileIds "
    128          "WHERE fileId = :fileId "
    129          ";"_ns;
    130 
    131      QM_TRY_UNWRAP(ResultStatement stmt,
    132                    ResultStatement::Create(aConn, clearFileIdsQuery));
    133 
    134      QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, fileId)));
    135 
    136      QM_TRY(QM_TO_RESULT(stmt.Execute()));
    137    }
    138 
    139    return Ok{};
    140  }(invalidFileIds)));
    141 
    142  return Ok{};
    143 }
    144 
    145 Result<Ok, QMResult> ClearInvalidMainFiles(
    146    ResultConnection& aConn, data::FileSystemFileManager& aFileManager) {
    147  // We cant't just clear all main files because if a file was accessed using
    148  // writable file stream a new main file was created which is not the same as
    149  // entry id.
    150 
    151  // Get all main files first.
    152  QM_TRY_INSPECT(
    153      const auto& allMainFiles,
    154      ([&aConn]() -> Result<nsTArray<std::pair<EntryId, FileId>>, QMResult> {
    155        const nsLiteralCString allMainFilesQuery =
    156            "SELECT handle, fileId FROM MainFiles;"_ns;
    157 
    158        QM_TRY_UNWRAP(ResultStatement stmt,
    159                      ResultStatement::Create(aConn, allMainFilesQuery));
    160 
    161        nsTArray<std::pair<EntryId, FileId>> mainFiles;
    162 
    163        while (true) {
    164          QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
    165          if (!moreResults) {
    166            break;
    167          }
    168 
    169          QM_TRY_UNWRAP(EntryId entryId,
    170                        stmt.GetEntryIdByColumn(/* Column */ 0u));
    171          QM_TRY_UNWRAP(FileId fileId, stmt.GetFileIdByColumn(/* Column */ 1u));
    172 
    173          mainFiles.AppendElement(std::pair<EntryId, FileId>(entryId, fileId));
    174        }
    175 
    176        return std::move(mainFiles);
    177      }()));
    178 
    179  // Filter out main files which have non-zero-sized files on disk.
    180  QM_TRY_INSPECT(
    181      const auto& invalidMainFiles,
    182      ([&aFileManager](const nsTArray<std::pair<EntryId, FileId>>& aMainFiles)
    183           -> Result<nsTArray<std::pair<EntryId, FileId>>, QMResult> {
    184        nsTArray<std::pair<EntryId, FileId>> mainFiles;
    185 
    186        for (const auto& mainFile : aMainFiles) {
    187          QM_TRY_UNWRAP(auto file, aFileManager.GetFile(mainFile.second));
    188 
    189          QM_TRY_INSPECT(const bool& exists,
    190                         QM_TO_RESULT_INVOKE_MEMBER(file, Exists));
    191 
    192          if (exists) {
    193            QM_TRY_INSPECT(const int64_t& fileSize,
    194                           QM_TO_RESULT_INVOKE_MEMBER(file, GetFileSize));
    195 
    196            if (fileSize != 0) {
    197              continue;
    198            }
    199 
    200            QM_TRY(QM_TO_RESULT(file->Remove(false)));
    201          }
    202 
    203          mainFiles.AppendElement(mainFile);
    204        }
    205 
    206        return std::move(mainFiles);
    207      }(allMainFiles)));
    208 
    209  // Finally, clear invalid main files.
    210  QM_TRY(([&aConn](const nsTArray<std::pair<EntryId, FileId>>& aMainFiles)
    211              -> Result<Ok, QMResult> {
    212    for (const auto& mainFile : aMainFiles) {
    213      const nsLiteralCString clearMainFilesQuery =
    214          "DELETE FROM MainFiles "
    215          "WHERE handle = :entryId AND fileId = :fileId "
    216          ";"_ns;
    217 
    218      QM_TRY_UNWRAP(ResultStatement stmt,
    219                    ResultStatement::Create(aConn, clearMainFilesQuery));
    220 
    221      QM_TRY(
    222          QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, mainFile.first)));
    223      QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, mainFile.second)));
    224 
    225      QM_TRY(QM_TO_RESULT(stmt.Execute()));
    226    }
    227 
    228    return Ok{};
    229  }(invalidMainFiles)));
    230 
    231  return Ok{};
    232 }
    233 
    234 nsresult ConnectUsagesToFileIds(ResultConnection& aConn) {
    235  QM_TRY(
    236      MOZ_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns)));
    237 
    238  auto turnForeignKeysBackOn = MakeScopeExit([&aConn]() {
    239    QM_WARNONLY_TRY(
    240        MOZ_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
    241  });
    242 
    243  QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn));
    244 
    245  QM_TRY(MOZ_TO_RESULT(
    246      aConn->ExecuteSimpleSQL("DROP TABLE IF EXISTS migrateUsages ;"_ns)));
    247 
    248  QM_TRY(MOZ_TO_RESULT(aConn->ExecuteSimpleSQL(
    249      "CREATE TABLE migrateUsages ( "
    250      "handle BLOB PRIMARY KEY, "
    251      "usage INTEGER NOT NULL DEFAULT 0, "
    252      "tracked BOOLEAN NOT NULL DEFAULT 0 CHECK (tracked IN (0, 1)), "
    253      "CONSTRAINT handles_are_fileIds "
    254      "FOREIGN KEY (handle) "
    255      "REFERENCES FileIds (fileId) "
    256      "ON DELETE CASCADE ) "
    257      ";"_ns)));
    258 
    259  QM_TRY(MOZ_TO_RESULT(aConn->ExecuteSimpleSQL(
    260      "INSERT INTO migrateUsages ( handle, usage, tracked ) "
    261      "SELECT handle, usage, tracked FROM Usages ;"_ns)));
    262 
    263  QM_TRY(MOZ_TO_RESULT(aConn->ExecuteSimpleSQL("DROP TABLE Usages;"_ns)));
    264 
    265  QM_TRY(MOZ_TO_RESULT(aConn->ExecuteSimpleSQL(
    266      "ALTER TABLE migrateUsages RENAME TO Usages;"_ns)));
    267 
    268  QM_TRY(
    269      MOZ_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_key_check;"_ns)));
    270 
    271  QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
    272 
    273  return NS_OK;
    274 }
    275 
    276 nsresult CreateEntryNamesView(ResultConnection& aConn) {
    277  return aConn->ExecuteSimpleSQL(
    278      "CREATE VIEW IF NOT EXISTS EntryNames AS "
    279      "SELECT isFile, handle, parent, name FROM Entries INNER JOIN ( "
    280      "SELECT 1 AS isFile, handle, name FROM Files UNION "
    281      "SELECT 0, handle, name FROM Directories ) "
    282      "USING (handle) "
    283      ";"_ns);
    284 }
    285 
    286 nsresult FixEntryIds(const ResultConnection& aConnection,
    287                     const EntryId& aRootEntry) {
    288  const nsLiteralCString calculateHashesQuery =
    289      "CREATE TEMPORARY TABLE EntryMigrationTable AS "
    290      "WITH RECURSIVE "
    291      "rehashMap( depth, isFile, handle, parent, name, hash ) AS ( "
    292      "SELECT 0, isFile, handle, parent, name, hashEntry( :rootEntry, name ) "
    293      "FROM EntryNames WHERE parent = :rootEntry UNION SELECT "
    294      "1 + depth, EntryNames.isFile, EntryNames.handle, EntryNames.parent, "
    295      "EntryNames.name, hashEntry( rehashMap.hash, EntryNames.name ) "
    296      "FROM rehashMap, EntryNames WHERE rehashMap.handle = EntryNames.parent ) "
    297      "SELECT depth, isFile, handle, parent, name, hash FROM rehashMap "
    298      ";"_ns;
    299 
    300  const nsLiteralCString createIndexByDepthQuery =
    301      "CREATE INDEX indexOnDepth ON EntryMigrationTable ( depth ); "_ns;
    302 
    303  // To avoid constraint violation, new entries are inserted under a temporary
    304  // parent.
    305 
    306  const nsLiteralCString insertTemporaryParentEntry =
    307      "INSERT INTO Entries ( handle, parent ) "
    308      "VALUES ( :tempParent, :rootEntry ) ;"_ns;
    309 
    310  const nsLiteralCString flagTemporaryParentAsDir =
    311      "INSERT INTO Directories ( handle, name ) "
    312      "VALUES ( :tempParent, 'temp' ) ;"_ns;
    313 
    314  const nsLiteralCString insertNewEntriesQuery =
    315      "INSERT INTO Entries ( handle, parent ) "
    316      "SELECT hash, :tempParent FROM EntryMigrationTable WHERE hash != handle "
    317      ";"_ns;
    318 
    319  const nsLiteralCString insertNewDirectoriesQuery =
    320      "INSERT INTO Directories ( handle, name ) "
    321      "SELECT hash, name FROM EntryMigrationTable "
    322      "WHERE isFile = 0 AND hash != handle "
    323      "ORDER BY depth "
    324      ";"_ns;
    325 
    326  const nsLiteralCString insertNewFilesQuery =
    327      "INSERT INTO Files ( handle, type, name ) "
    328      "SELECT EntryMigrationTable.hash, Files.type, EntryMigrationTable.name "
    329      "FROM EntryMigrationTable INNER JOIN Files USING (handle) "
    330      "WHERE EntryMigrationTable.isFile = 1 AND hash != handle "
    331      ";"_ns;
    332 
    333  const nsLiteralCString updateFileMappingsQuery =
    334      "UPDATE FileIds SET handle = hash "
    335      "FROM ( SELECT handle, hash FROM EntryMigrationTable WHERE hash != "
    336      "handle ) "
    337      "AS replacement WHERE FileIds.handle = replacement.handle "
    338      ";"_ns;
    339 
    340  const nsLiteralCString updateMainFilesQuery =
    341      "UPDATE MainFiles SET handle = hash "
    342      "FROM ( SELECT handle, hash FROM EntryMigrationTable WHERE hash != "
    343      "handle ) "
    344      "AS replacement WHERE MainFiles.handle = replacement.handle "
    345      ";"_ns;
    346 
    347  // Now fix the parents.
    348  const nsLiteralCString updateEntryMappingsQuery =
    349      "UPDATE Entries SET parent = hash "
    350      "FROM ( SELECT Lhs.hash AS handle, Rhs.hash AS hash, Lhs.depth AS depth "
    351      "FROM EntryMigrationTable AS Lhs "
    352      "INNER JOIN EntryMigrationTable AS Rhs "
    353      "ON Rhs.handle = Lhs.parent ORDER BY depth ) AS replacement "
    354      "WHERE Entries.handle = replacement.handle "
    355      "AND Entries.parent = :tempParent "
    356      ";"_ns;
    357 
    358  const nsLiteralCString cleanupOldEntriesQuery =
    359      "DELETE FROM Entries WHERE handle IN "
    360      "( SELECT handle FROM EntryMigrationTable WHERE hash != handle ) "
    361      ";"_ns;
    362 
    363  const nsLiteralCString cleanupTemporaryParent =
    364      "DELETE FROM Entries WHERE handle = :tempParent ;"_ns;
    365 
    366  const nsLiteralCString dropIndexByDepthQuery =
    367      "DROP INDEX indexOnDepth ; "_ns;
    368 
    369  // Index is automatically deleted
    370  const nsLiteralCString cleanupTemporaries =
    371      "DROP TABLE EntryMigrationTable ;"_ns;
    372 
    373  EntryId tempParent(nsCString(nsID::GenerateUUID().ToString().get()));
    374 
    375  nsCOMPtr<mozIStorageFunction> rehashFunction =
    376      new data::FileSystemHashStorageFunction();
    377  QM_TRY(MOZ_TO_RESULT(aConnection->CreateFunction("hashEntry"_ns,
    378                                                   /* number of arguments */ 2,
    379                                                   rehashFunction)));
    380  auto finallyRemoveFunction = MakeScopeExit([&aConnection]() {
    381    QM_WARNONLY_TRY(MOZ_TO_RESULT(aConnection->RemoveFunction("hashEntry"_ns)));
    382  });
    383 
    384  // We need this to make sure the old entries get removed
    385  QM_TRY(MOZ_TO_RESULT(
    386      aConnection->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
    387 
    388  QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConnection));
    389 
    390  {
    391    QM_TRY_UNWRAP(ResultStatement stmt,
    392                  ResultStatement::Create(aConnection, calculateHashesQuery));
    393    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("rootEntry"_ns, aRootEntry)));
    394    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    395  }
    396 
    397  QM_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL(createIndexByDepthQuery)));
    398 
    399  {
    400    QM_TRY_UNWRAP(
    401        ResultStatement stmt,
    402        ResultStatement::Create(aConnection, insertTemporaryParentEntry));
    403    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent)));
    404    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("rootEntry"_ns, aRootEntry)));
    405    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    406  }
    407 
    408  {
    409    QM_TRY_UNWRAP(
    410        ResultStatement stmt,
    411        ResultStatement::Create(aConnection, flagTemporaryParentAsDir));
    412    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent)));
    413    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    414  }
    415 
    416  {
    417    QM_TRY_UNWRAP(ResultStatement stmt,
    418                  ResultStatement::Create(aConnection, insertNewEntriesQuery));
    419    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent)));
    420    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    421  }
    422 
    423  {
    424    QM_TRY_UNWRAP(
    425        ResultStatement stmt,
    426        ResultStatement::Create(aConnection, insertNewDirectoriesQuery));
    427    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    428  }
    429 
    430  {
    431    QM_TRY_UNWRAP(ResultStatement stmt,
    432                  ResultStatement::Create(aConnection, insertNewFilesQuery));
    433    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    434  }
    435 
    436  {
    437    QM_TRY_UNWRAP(
    438        ResultStatement stmt,
    439        ResultStatement::Create(aConnection, updateFileMappingsQuery));
    440    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    441  }
    442 
    443  {
    444    QM_TRY_UNWRAP(ResultStatement stmt,
    445                  ResultStatement::Create(aConnection, updateMainFilesQuery));
    446    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    447  }
    448 
    449  {
    450    QM_TRY_UNWRAP(
    451        ResultStatement stmt,
    452        ResultStatement::Create(aConnection, updateEntryMappingsQuery));
    453    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent)));
    454    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    455  }
    456 
    457  {
    458    QM_TRY_UNWRAP(ResultStatement stmt,
    459                  ResultStatement::Create(aConnection, cleanupOldEntriesQuery));
    460    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    461  }
    462 
    463  {
    464    QM_TRY_UNWRAP(ResultStatement stmt,
    465                  ResultStatement::Create(aConnection, cleanupTemporaryParent));
    466    QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent)));
    467    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    468  }
    469 
    470  QM_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL(dropIndexByDepthQuery)));
    471 
    472  {
    473    QM_TRY_UNWRAP(ResultStatement stmt,
    474                  ResultStatement::Create(aConnection, cleanupTemporaries));
    475    QM_TRY(QM_TO_RESULT(stmt.Execute()));
    476  }
    477 
    478  QM_TRY(QM_TO_RESULT(transaction.Commit()));
    479 
    480  QM_WARNONLY_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL("VACUUM;"_ns)));
    481 
    482  return NS_OK;
    483 }
    484 
    485 }  // namespace
    486 
    487 Result<DatabaseVersion, QMResult> SchemaVersion002::InitializeConnection(
    488    ResultConnection& aConn, data::FileSystemFileManager& aFileManager,
    489    const Origin& aOrigin) {
    490  QM_TRY_UNWRAP(const bool wasEmpty, CheckIfEmpty(aConn));
    491 
    492  DatabaseVersion currentVersion = 0;
    493 
    494  if (wasEmpty) {
    495    QM_TRY(QM_TO_RESULT(SetEncoding(aConn)));
    496  } else {
    497    QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(&currentVersion)));
    498  }
    499 
    500  if (currentVersion < sVersion) {
    501    MOZ_ASSERT_IF(0 != currentVersion, 1 == currentVersion);
    502 
    503    QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn));
    504 
    505    if (0 == currentVersion) {
    506      QM_TRY(QM_TO_RESULT(SchemaVersion001::CreateTables(aConn, aOrigin)));
    507    }
    508 
    509    QM_TRY(QM_TO_RESULT(CreateFileIds(aConn)));
    510 
    511    if (!wasEmpty) {
    512      QM_TRY(QM_TO_RESULT(PopulateFileIds(aConn)));
    513    }
    514 
    515    QM_TRY(QM_TO_RESULT(ConnectUsagesToFileIds(aConn)));
    516 
    517    QM_TRY(QM_TO_RESULT(CreateMainFiles(aConn)));
    518    if (!wasEmpty) {
    519      QM_TRY(QM_TO_RESULT(PopulateMainFiles(aConn)));
    520    }
    521 
    522    QM_TRY(QM_TO_RESULT(CreateEntryNamesView(aConn)));
    523 
    524    QM_TRY(QM_TO_RESULT(aConn->SetSchemaVersion(sVersion)));
    525 
    526    QM_TRY(QM_TO_RESULT(transaction.Commit()));
    527 
    528    if (!wasEmpty) {
    529      QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL("VACUUM;"_ns)));
    530    }
    531  }
    532 
    533  // The upgrade from version 1 to version 2 was buggy, so we have to check if
    534  // the Usages table still references the Files table which is a sign that
    535  // the upgrade wasn't complete. This extra query has only negligible perf
    536  // impact. See bug 1847989.
    537  auto UsagesTableRefsFilesTable = [&aConn]() -> Result<bool, QMResult> {
    538    const nsLiteralCString query =
    539        "SELECT pragma_foreign_key_list.'table'=='Files' "
    540        "FROM pragma_foreign_key_list('Usages');"_ns;
    541 
    542    QM_TRY_UNWRAP(ResultStatement stmt, ResultStatement::Create(aConn, query));
    543 
    544    return stmt.YesOrNoQuery();
    545  };
    546 
    547  QM_TRY_UNWRAP(auto usagesTableRefsFilesTable, UsagesTableRefsFilesTable());
    548 
    549  if (usagesTableRefsFilesTable) {
    550    QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn));
    551 
    552    // The buggy upgrade didn't call PopulateFileIds, ConnectUsagesToFileIds
    553    // and PopulateMainFiles was completely missing. Since invalid file ids
    554    // and main files could be inserted when the profile was broken, we need
    555    // to clear them before populating.
    556    QM_TRY(ClearInvalidFileIds(aConn, aFileManager));
    557    QM_TRY(QM_TO_RESULT(PopulateFileIds(aConn)));
    558    QM_TRY(QM_TO_RESULT(ConnectUsagesToFileIds(aConn)));
    559    QM_TRY(ClearInvalidMainFiles(aConn, aFileManager));
    560    QM_TRY(QM_TO_RESULT(PopulateMainFiles(aConn)));
    561 
    562    QM_TRY(QM_TO_RESULT(transaction.Commit()));
    563 
    564    QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL("VACUUM;"_ns)));
    565 
    566    QM_TRY_UNWRAP(usagesTableRefsFilesTable, UsagesTableRefsFilesTable());
    567    MOZ_ASSERT(!usagesTableRefsFilesTable);
    568  }
    569 
    570  // In schema version 001, entryId was unique but not necessarily related to
    571  // a path. For schema 002, we have to fix all entryIds to be derived from
    572  // the underlying path.
    573  auto OneTimeRehashingDone = [&aConn]() -> Result<bool, QMResult> {
    574    const nsLiteralCString query =
    575        "SELECT EXISTS (SELECT 1 FROM sqlite_master "
    576        "WHERE type='table' AND name='RehashedFrom001to002' ) ;"_ns;
    577 
    578    QM_TRY_UNWRAP(ResultStatement stmt, ResultStatement::Create(aConn, query));
    579 
    580    return stmt.YesOrNoQuery();
    581  };
    582 
    583  QM_TRY_UNWRAP(auto oneTimeRehashingDone, OneTimeRehashingDone());
    584 
    585  if (!oneTimeRehashingDone) {
    586    const nsLiteralCString findRootEntry =
    587        "SELECT handle FROM Entries WHERE parent IS NULL ;"_ns;
    588 
    589    EntryId rootId;
    590    {
    591      QM_TRY_UNWRAP(ResultStatement stmt,
    592                    ResultStatement::Create(aConn, findRootEntry));
    593 
    594      QM_TRY_UNWRAP(DebugOnly<bool> moreResults, stmt.ExecuteStep());
    595      MOZ_ASSERT(moreResults);
    596 
    597      QM_TRY_UNWRAP(rootId, stmt.GetEntryIdByColumn(/* Column */ 0u));
    598    }
    599 
    600    MOZ_ASSERT(!rootId.IsEmpty());
    601 
    602    QM_TRY(QM_TO_RESULT(FixEntryIds(aConn, rootId)));
    603 
    604    QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL(
    605        "CREATE TABLE RehashedFrom001to002 (id INTEGER PRIMARY KEY);"_ns)));
    606 
    607    QM_TRY_UNWRAP(DebugOnly<bool> isDoneNow, OneTimeRehashingDone());
    608    MOZ_ASSERT(isDoneNow);
    609  }
    610 
    611  QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
    612 
    613  QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(&currentVersion)));
    614 
    615  return currentVersion;
    616 }
    617 
    618 }  // namespace mozilla::dom::fs