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(¤tVersion))); 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(¤tVersion))); 614 615 return currentVersion; 616 } 617 618 } // namespace mozilla::dom::fs