Bug 1855386: Remove nsNavBookmarks::ResultNodeForContainer() r=mak
[gecko.git] / toolkit / components / places / Database.cpp
blobbd7e1cc2d908637ea67280f062ac4a575826e0a1
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "mozilla/ArrayUtils.h"
6 #include "mozilla/Attributes.h"
7 #include "mozilla/DebugOnly.h"
8 #include "mozilla/ScopeExit.h"
9 #include "mozilla/SpinEventLoopUntil.h"
10 #include "mozilla/StaticPrefs_places.h"
12 #include "Database.h"
14 #include "nsIInterfaceRequestorUtils.h"
15 #include "nsIFile.h"
17 #include "nsNavBookmarks.h"
18 #include "nsNavHistory.h"
19 #include "nsPlacesTables.h"
20 #include "nsPlacesIndexes.h"
21 #include "nsPlacesTriggers.h"
22 #include "nsPlacesMacros.h"
23 #include "nsVariant.h"
24 #include "SQLFunctions.h"
25 #include "Helpers.h"
26 #include "nsFaviconService.h"
28 #include "nsAppDirectoryServiceDefs.h"
29 #include "nsDirectoryServiceUtils.h"
30 #include "prenv.h"
31 #include "prsystem.h"
32 #include "nsPrintfCString.h"
33 #include "mozilla/Preferences.h"
34 #include "mozilla/Services.h"
35 #include "mozilla/Unused.h"
36 #include "mozIStorageService.h"
37 #include "prtime.h"
39 #include "nsXULAppAPI.h"
41 // Time between corrupt database backups.
42 #define RECENT_BACKUP_TIME_MICROSEC (int64_t)86400 * PR_USEC_PER_SEC // 24H
44 // Filename of the database.
45 #define DATABASE_FILENAME u"places.sqlite"_ns
46 // Filename of the icons database.
47 #define DATABASE_FAVICONS_FILENAME u"favicons.sqlite"_ns
49 // Set to the database file name when it was found corrupt by a previous
50 // maintenance run.
51 #define PREF_FORCE_DATABASE_REPLACEMENT \
52 "places.database.replaceDatabaseOnStartup"
54 // Whether on corruption we should try to fix the database by cloning it.
55 #define PREF_DATABASE_CLONEONCORRUPTION "places.database.cloneOnCorruption"
57 // Set to specify the size of the places database growth increments in kibibytes
58 #define PREF_GROWTH_INCREMENT_KIB "places.database.growthIncrementKiB"
60 // Set to disable the default robust storage and use volatile, in-memory
61 // storage without robust transaction flushing guarantees. This makes
62 // SQLite use much less I/O at the cost of losing data when things crash.
63 // The pref is only honored if an environment variable is set. The env
64 // variable is intentionally named something scary to help prevent someone
65 // from thinking it is a useful performance optimization they should enable.
66 #define PREF_DISABLE_DURABILITY "places.database.disableDurability"
68 #define PREF_PREVIEWS_ENABLED "places.previews.enabled"
70 #define ENV_ALLOW_CORRUPTION \
71 "ALLOW_PLACES_DATABASE_TO_LOSE_DATA_AND_BECOME_CORRUPT"
73 // Maximum size for the WAL file.
74 // For performance reasons this should be as large as possible, so that more
75 // transactions can fit into it, and the checkpoint cost is paid less often.
76 // At the same time, since we use synchronous = NORMAL, an fsync happens only
77 // at checkpoint time, so we don't want the WAL to grow too much and risk to
78 // lose all the contained transactions on a crash.
79 #define DATABASE_MAX_WAL_BYTES 2048000
81 // Since exceeding the journal limit will cause a truncate, we allow a slightly
82 // larger limit than DATABASE_MAX_WAL_BYTES to reduce the number of truncates.
83 // This is the number of bytes the journal can grow over the maximum wal size
84 // before being truncated.
85 #define DATABASE_JOURNAL_OVERHEAD_BYTES 2048000
87 #define BYTES_PER_KIBIBYTE 1024
89 // How much time Sqlite can wait before returning a SQLITE_BUSY error.
90 #define DATABASE_BUSY_TIMEOUT_MS 100
92 // This annotation is no longer used & is obsolete, but here for migration.
93 #define LAST_USED_ANNO "bookmarkPropertiesDialog/folderLastUsed"_ns
94 // This is key in the meta table that the LAST_USED_ANNO is migrated to.
95 #define LAST_USED_FOLDERS_META_KEY "places/bookmarks/edit/lastusedfolder"_ns
97 // We use a fixed title for the mobile root to avoid marking the database as
98 // corrupt if we can't look up the localized title in the string bundle. Sync
99 // sets the title to the localized version when it creates the left pane query.
100 #define MOBILE_ROOT_TITLE "mobile"
102 // Legacy item annotation used by the old Sync engine.
103 #define SYNC_PARENT_ANNO "sync/parent"
105 using namespace mozilla;
107 namespace mozilla::places {
109 namespace {
111 ////////////////////////////////////////////////////////////////////////////////
112 //// Helpers
115 * Get the filename for a corrupt database.
117 nsString getCorruptFilename(const nsString& aDbFilename) {
118 return aDbFilename + u".corrupt"_ns;
121 * Get the filename for a recover database.
123 nsString getRecoverFilename(const nsString& aDbFilename) {
124 return aDbFilename + u".recover"_ns;
128 * Checks whether exists a corrupt database file created not longer than
129 * RECENT_BACKUP_TIME_MICROSEC ago.
131 bool isRecentCorruptFile(const nsCOMPtr<nsIFile>& aCorruptFile) {
132 MOZ_ASSERT(NS_IsMainThread());
133 bool fileExists = false;
134 if (NS_FAILED(aCorruptFile->Exists(&fileExists)) || !fileExists) {
135 return false;
137 PRTime lastMod = 0;
138 return NS_SUCCEEDED(aCorruptFile->GetLastModifiedTime(&lastMod)) &&
139 lastMod > 0 && (PR_Now() - lastMod) <= RECENT_BACKUP_TIME_MICROSEC;
143 * Removes a file, optionally adding a suffix to the file name.
145 void RemoveFileSwallowsErrors(const nsCOMPtr<nsIFile>& aFile,
146 const nsString& aSuffix = u""_ns) {
147 nsCOMPtr<nsIFile> file;
148 MOZ_ALWAYS_SUCCEEDS(aFile->Clone(getter_AddRefs(file)));
149 if (!aSuffix.IsEmpty()) {
150 nsAutoString newFileName;
151 file->GetLeafName(newFileName);
152 newFileName.Append(aSuffix);
153 MOZ_ALWAYS_SUCCEEDS(file->SetLeafName(newFileName));
155 DebugOnly<nsresult> rv = file->Remove(false);
156 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to remove file.");
160 * Sets the connection journal mode to one of the JOURNAL_* types.
162 * @param aDBConn
163 * The database connection.
164 * @param aJournalMode
165 * One of the JOURNAL_* types.
166 * @returns the current journal mode.
167 * @note this may return a different journal mode than the required one, since
168 * setting it may fail.
170 enum JournalMode SetJournalMode(nsCOMPtr<mozIStorageConnection>& aDBConn,
171 enum JournalMode aJournalMode) {
172 MOZ_ASSERT(NS_IsMainThread());
173 nsAutoCString journalMode;
174 switch (aJournalMode) {
175 default:
176 MOZ_FALLTHROUGH_ASSERT("Trying to set an unknown journal mode.");
177 // Fall through to the default DELETE journal.
178 case JOURNAL_DELETE:
179 journalMode.AssignLiteral("delete");
180 break;
181 case JOURNAL_TRUNCATE:
182 journalMode.AssignLiteral("truncate");
183 break;
184 case JOURNAL_MEMORY:
185 journalMode.AssignLiteral("memory");
186 break;
187 case JOURNAL_WAL:
188 journalMode.AssignLiteral("wal");
189 break;
192 nsCOMPtr<mozIStorageStatement> statement;
193 nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = ");
194 query.Append(journalMode);
195 aDBConn->CreateStatement(query, getter_AddRefs(statement));
196 NS_ENSURE_TRUE(statement, JOURNAL_DELETE);
198 bool hasResult = false;
199 if (NS_SUCCEEDED(statement->ExecuteStep(&hasResult)) && hasResult &&
200 NS_SUCCEEDED(statement->GetUTF8String(0, journalMode))) {
201 if (journalMode.EqualsLiteral("delete")) {
202 return JOURNAL_DELETE;
204 if (journalMode.EqualsLiteral("truncate")) {
205 return JOURNAL_TRUNCATE;
207 if (journalMode.EqualsLiteral("memory")) {
208 return JOURNAL_MEMORY;
210 if (journalMode.EqualsLiteral("wal")) {
211 return JOURNAL_WAL;
213 MOZ_ASSERT(false, "Got an unknown journal mode.");
216 return JOURNAL_DELETE;
219 nsresult CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
220 const nsCString& aRootName, const nsCString& aGuid,
221 const nsCString& titleString, const int32_t position,
222 int64_t& newId) {
223 MOZ_ASSERT(NS_IsMainThread());
225 // A single creation timestamp for all roots so that the root folder's
226 // last modification time isn't earlier than its childrens' creation time.
227 static PRTime timestamp = 0;
228 if (!timestamp) timestamp = RoundedPRNow();
230 // Create a new bookmark folder for the root.
231 nsCOMPtr<mozIStorageStatement> stmt;
232 nsresult rv = aDBConn->CreateStatement(
233 nsLiteralCString(
234 "INSERT INTO moz_bookmarks "
235 "(type, position, title, dateAdded, lastModified, guid, parent, "
236 "syncChangeCounter, syncStatus) "
237 "VALUES (:item_type, :item_position, :item_title,"
238 ":date_added, :last_modified, :guid, "
239 "IFNULL((SELECT id FROM moz_bookmarks WHERE parent = 0), 0), "
240 "1, :sync_status)"),
241 getter_AddRefs(stmt));
242 if (NS_FAILED(rv)) return rv;
244 rv = stmt->BindInt32ByName("item_type"_ns,
245 nsINavBookmarksService::TYPE_FOLDER);
246 if (NS_FAILED(rv)) return rv;
247 rv = stmt->BindInt32ByName("item_position"_ns, position);
248 if (NS_FAILED(rv)) return rv;
249 rv = stmt->BindUTF8StringByName("item_title"_ns, titleString);
250 if (NS_FAILED(rv)) return rv;
251 rv = stmt->BindInt64ByName("date_added"_ns, timestamp);
252 if (NS_FAILED(rv)) return rv;
253 rv = stmt->BindInt64ByName("last_modified"_ns, timestamp);
254 if (NS_FAILED(rv)) return rv;
255 rv = stmt->BindUTF8StringByName("guid"_ns, aGuid);
256 if (NS_FAILED(rv)) return rv;
257 rv = stmt->BindInt32ByName("sync_status"_ns,
258 nsINavBookmarksService::SYNC_STATUS_NEW);
259 if (NS_FAILED(rv)) return rv;
260 rv = stmt->Execute();
261 if (NS_FAILED(rv)) return rv;
263 newId = nsNavBookmarks::sLastInsertedItemId;
264 return NS_OK;
267 nsresult SetupDurability(nsCOMPtr<mozIStorageConnection>& aDBConn,
268 int32_t aDBPageSize) {
269 nsresult rv;
270 if (PR_GetEnv(ENV_ALLOW_CORRUPTION) &&
271 Preferences::GetBool(PREF_DISABLE_DURABILITY, false)) {
272 // Volatile storage was requested. Use the in-memory journal (no
273 // filesystem I/O) and don't sync the filesystem after writing.
274 SetJournalMode(aDBConn, JOURNAL_MEMORY);
275 rv = aDBConn->ExecuteSimpleSQL("PRAGMA synchronous = OFF"_ns);
276 NS_ENSURE_SUCCESS(rv, rv);
277 } else {
278 // Be sure to set journal mode after page_size. WAL would prevent the
279 // change otherwise.
280 if (JOURNAL_WAL == SetJournalMode(aDBConn, JOURNAL_WAL)) {
281 // Set the WAL journal size limit.
282 int32_t checkpointPages =
283 static_cast<int32_t>(DATABASE_MAX_WAL_BYTES / aDBPageSize);
284 nsAutoCString checkpointPragma("PRAGMA wal_autocheckpoint = ");
285 checkpointPragma.AppendInt(checkpointPages);
286 rv = aDBConn->ExecuteSimpleSQL(checkpointPragma);
287 NS_ENSURE_SUCCESS(rv, rv);
288 } else {
289 // Ignore errors, if we fail here the database could be considered corrupt
290 // and we won't be able to go on, even if it's just matter of a bogus file
291 // system. The default mode (DELETE) will be fine in such a case.
292 (void)SetJournalMode(aDBConn, JOURNAL_TRUNCATE);
294 // Set synchronous to FULL to ensure maximum data integrity, even in
295 // case of crashes or unclean shutdowns.
296 rv = aDBConn->ExecuteSimpleSQL("PRAGMA synchronous = FULL"_ns);
297 NS_ENSURE_SUCCESS(rv, rv);
301 // The journal is usually free to grow for performance reasons, but it never
302 // shrinks back. Since the space taken may be problematic, limit its size.
303 nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
304 journalSizePragma.AppendInt(DATABASE_MAX_WAL_BYTES +
305 DATABASE_JOURNAL_OVERHEAD_BYTES);
306 (void)aDBConn->ExecuteSimpleSQL(journalSizePragma);
308 // Grow places in |growthIncrementKiB| increments to limit fragmentation on
309 // disk. By default, it's 5 MB.
310 int32_t growthIncrementKiB =
311 Preferences::GetInt(PREF_GROWTH_INCREMENT_KIB, 5 * BYTES_PER_KIBIBYTE);
312 if (growthIncrementKiB > 0) {
313 (void)aDBConn->SetGrowthIncrement(growthIncrementKiB * BYTES_PER_KIBIBYTE,
314 ""_ns);
316 return NS_OK;
319 nsresult AttachDatabase(nsCOMPtr<mozIStorageConnection>& aDBConn,
320 const nsACString& aPath, const nsACString& aName) {
321 nsCOMPtr<mozIStorageStatement> stmt;
322 nsresult rv = aDBConn->CreateStatement("ATTACH DATABASE :path AS "_ns + aName,
323 getter_AddRefs(stmt));
324 NS_ENSURE_SUCCESS(rv, rv);
325 rv = stmt->BindUTF8StringByName("path"_ns, aPath);
326 NS_ENSURE_SUCCESS(rv, rv);
327 rv = stmt->Execute();
328 NS_ENSURE_SUCCESS(rv, rv);
330 // The journal limit must be set apart for each database.
331 nsAutoCString journalSizePragma("PRAGMA favicons.journal_size_limit = ");
332 journalSizePragma.AppendInt(DATABASE_MAX_WAL_BYTES +
333 DATABASE_JOURNAL_OVERHEAD_BYTES);
334 Unused << aDBConn->ExecuteSimpleSQL(journalSizePragma);
336 return NS_OK;
339 } // namespace
341 ////////////////////////////////////////////////////////////////////////////////
342 //// Database
344 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(Database, gDatabase)
346 NS_IMPL_ISUPPORTS(Database, nsIObserver, nsISupportsWeakReference)
348 Database::Database()
349 : mMainThreadStatements(mMainConn),
350 mMainThreadAsyncStatements(mMainConn),
351 mAsyncThreadStatements(mMainConn),
352 mDBPageSize(0),
353 mDatabaseStatus(nsINavHistoryService::DATABASE_STATUS_OK),
354 mClosed(false),
355 mClientsShutdown(new ClientsShutdownBlocker()),
356 mConnectionShutdown(new ConnectionShutdownBlocker(this)),
357 mMaxUrlLength(0),
358 mCacheObservers(TOPIC_PLACES_INIT_COMPLETE),
359 mRootId(-1),
360 mMenuRootId(-1),
361 mTagsRootId(-1),
362 mUnfiledRootId(-1),
363 mToolbarRootId(-1),
364 mMobileRootId(-1) {
365 MOZ_ASSERT(!XRE_IsContentProcess(),
366 "Cannot instantiate Places in the content process");
367 // Attempting to create two instances of the service?
368 MOZ_ASSERT(!gDatabase);
369 gDatabase = this;
372 already_AddRefed<nsIAsyncShutdownClient>
373 Database::GetProfileChangeTeardownPhase() {
374 nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc =
375 services::GetAsyncShutdownService();
376 MOZ_ASSERT(asyncShutdownSvc);
377 if (NS_WARN_IF(!asyncShutdownSvc)) {
378 return nullptr;
381 // Consumers of Places should shutdown before us, at profile-change-teardown.
382 nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
383 DebugOnly<nsresult> rv =
384 asyncShutdownSvc->GetProfileChangeTeardown(getter_AddRefs(shutdownPhase));
385 MOZ_ASSERT(NS_SUCCEEDED(rv));
386 return shutdownPhase.forget();
389 already_AddRefed<nsIAsyncShutdownClient>
390 Database::GetProfileBeforeChangePhase() {
391 nsCOMPtr<nsIAsyncShutdownService> asyncShutdownSvc =
392 services::GetAsyncShutdownService();
393 MOZ_ASSERT(asyncShutdownSvc);
394 if (NS_WARN_IF(!asyncShutdownSvc)) {
395 return nullptr;
398 // Consumers of Places should shutdown before us, at profile-change-teardown.
399 nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase;
400 DebugOnly<nsresult> rv =
401 asyncShutdownSvc->GetProfileBeforeChange(getter_AddRefs(shutdownPhase));
402 MOZ_ASSERT(NS_SUCCEEDED(rv));
403 return shutdownPhase.forget();
406 Database::~Database() = default;
408 already_AddRefed<mozIStorageAsyncStatement> Database::GetAsyncStatement(
409 const nsACString& aQuery) {
410 if (PlacesShutdownBlocker::sIsStarted || NS_FAILED(EnsureConnection())) {
411 return nullptr;
414 MOZ_ASSERT(NS_IsMainThread());
415 return mMainThreadAsyncStatements.GetCachedStatement(aQuery);
418 already_AddRefed<mozIStorageStatement> Database::GetStatement(
419 const nsACString& aQuery) {
420 if (PlacesShutdownBlocker::sIsStarted) {
421 return nullptr;
423 if (NS_IsMainThread()) {
424 if (NS_FAILED(EnsureConnection())) {
425 return nullptr;
427 return mMainThreadStatements.GetCachedStatement(aQuery);
429 // In the async case, the connection must have been started on the main-thread
430 // already.
431 MOZ_ASSERT(mMainConn);
432 return mAsyncThreadStatements.GetCachedStatement(aQuery);
435 already_AddRefed<nsIAsyncShutdownClient> Database::GetClientsShutdown() {
436 if (mClientsShutdown) return mClientsShutdown->GetClient();
437 return nullptr;
440 already_AddRefed<nsIAsyncShutdownClient> Database::GetConnectionShutdown() {
441 if (mConnectionShutdown) return mConnectionShutdown->GetClient();
442 return nullptr;
445 // static
446 already_AddRefed<Database> Database::GetDatabase() {
447 if (PlacesShutdownBlocker::sIsStarted) {
448 return nullptr;
450 return GetSingleton();
453 nsresult Database::Init() {
454 MOZ_ASSERT(NS_IsMainThread());
456 // DO NOT FAIL HERE, otherwise we would never break the cycle between this
457 // object and the shutdown blockers, causing unexpected leaks.
460 // First of all Places clients should block profile-change-teardown.
461 nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase =
462 GetProfileChangeTeardownPhase();
463 MOZ_ASSERT(shutdownPhase);
464 if (shutdownPhase) {
465 nsresult rv = shutdownPhase->AddBlocker(
466 static_cast<nsIAsyncShutdownBlocker*>(mClientsShutdown.get()),
467 NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns);
468 if (NS_FAILED(rv)) {
469 // Might occur if we're already shutting down, see bug#1753165
470 PlacesShutdownBlocker::sIsStarted = true;
471 NS_WARNING("Cannot add shutdown blocker for profile-change-teardown");
477 // Then connection closing should block profile-before-change.
478 nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase =
479 GetProfileBeforeChangePhase();
480 MOZ_ASSERT(shutdownPhase);
481 if (shutdownPhase) {
482 nsresult rv = shutdownPhase->AddBlocker(
483 static_cast<nsIAsyncShutdownBlocker*>(mConnectionShutdown.get()),
484 NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, u""_ns);
485 if (NS_FAILED(rv)) {
486 // Might occur if we're already shutting down, see bug#1753165
487 PlacesShutdownBlocker::sIsStarted = true;
488 NS_WARNING("Cannot add shutdown blocker for profile-before-change");
493 // Finally observe profile shutdown notifications.
494 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
495 if (os) {
496 (void)os->AddObserver(this, TOPIC_PROFILE_CHANGE_TEARDOWN, true);
498 return NS_OK;
501 nsresult Database::EnsureConnection() {
502 // Run this only once.
503 if (mMainConn ||
504 mDatabaseStatus == nsINavHistoryService::DATABASE_STATUS_LOCKED) {
505 return NS_OK;
507 // Don't try to create a database too late.
508 if (PlacesShutdownBlocker::sIsStarted) {
509 return NS_ERROR_FAILURE;
512 MOZ_ASSERT(NS_IsMainThread(),
513 "Database initialization must happen on the main-thread");
516 bool initSucceeded = false;
517 auto notify = MakeScopeExit([&]() {
518 // If the database connection cannot be opened, it may just be locked
519 // by third parties. Set a locked state.
520 if (!initSucceeded) {
521 mMainConn = nullptr;
522 mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_LOCKED;
524 // Notify at the next tick, to avoid re-entrancy problems.
525 NS_DispatchToMainThread(
526 NewRunnableMethod("places::Database::EnsureConnection()", this,
527 &Database::NotifyConnectionInitalized));
530 nsCOMPtr<mozIStorageService> storage =
531 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
532 NS_ENSURE_STATE(storage);
534 nsCOMPtr<nsIFile> profileDir;
535 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
536 getter_AddRefs(profileDir));
537 NS_ENSURE_SUCCESS(rv, rv);
539 nsCOMPtr<nsIFile> databaseFile;
540 rv = profileDir->Clone(getter_AddRefs(databaseFile));
541 NS_ENSURE_SUCCESS(rv, rv);
542 rv = databaseFile->Append(DATABASE_FILENAME);
543 NS_ENSURE_SUCCESS(rv, rv);
544 bool databaseExisted = false;
545 rv = databaseFile->Exists(&databaseExisted);
546 NS_ENSURE_SUCCESS(rv, rv);
548 nsAutoString corruptDbName;
549 if (NS_SUCCEEDED(Preferences::GetString(PREF_FORCE_DATABASE_REPLACEMENT,
550 corruptDbName)) &&
551 !corruptDbName.IsEmpty()) {
552 // If this pref is set, maintenance required a database replacement, due
553 // to integrity corruption. Be sure to clear the pref to avoid handling it
554 // more than once.
555 (void)Preferences::ClearUser(PREF_FORCE_DATABASE_REPLACEMENT);
557 // The database is corrupt, backup and replace it with a new one.
558 nsCOMPtr<nsIFile> fileToBeReplaced;
559 bool fileExists = false;
560 if (NS_SUCCEEDED(profileDir->Clone(getter_AddRefs(fileToBeReplaced))) &&
561 NS_SUCCEEDED(fileToBeReplaced->Exists(&fileExists)) && fileExists) {
562 rv = BackupAndReplaceDatabaseFile(storage, corruptDbName, true, false);
563 NS_ENSURE_SUCCESS(rv, rv);
567 // Open the database file. If it does not exist a new one will be created.
568 // Use an unshared connection, it will consume more memory but avoid shared
569 // cache contentions across threads.
570 rv = storage->OpenUnsharedDatabase(databaseFile,
571 mozIStorageService::CONNECTION_DEFAULT,
572 getter_AddRefs(mMainConn));
573 if (NS_SUCCEEDED(rv) && !databaseExisted) {
574 mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CREATE;
575 } else if (rv == NS_ERROR_FILE_CORRUPTED) {
576 // The database is corrupt, backup and replace it with a new one.
577 rv = BackupAndReplaceDatabaseFile(storage, DATABASE_FILENAME, true, true);
578 // Fallback to catch-all handler.
580 NS_ENSURE_SUCCESS(rv, rv);
582 // Initialize the database schema. In case of failure the existing schema
583 // is is corrupt or incoherent, thus the database should be replaced.
584 bool databaseMigrated = false;
585 rv = SetupDatabaseConnection(storage);
586 bool shouldTryToCloneDb = true;
587 if (NS_SUCCEEDED(rv)) {
588 // Failing to initialize the schema may indicate a corruption.
589 rv = InitSchema(&databaseMigrated);
590 if (NS_FAILED(rv)) {
591 // Cloning the db on a schema migration may not be a good idea, since we
592 // may end up cloning the schema problems.
593 shouldTryToCloneDb = false;
594 if (rv == NS_ERROR_STORAGE_BUSY || rv == NS_ERROR_FILE_IS_LOCKED ||
595 rv == NS_ERROR_FILE_NO_DEVICE_SPACE ||
596 rv == NS_ERROR_OUT_OF_MEMORY) {
597 // The database is not corrupt, though some migration step failed.
598 // This may be caused by concurrent use of sync and async Storage APIs
599 // or by a system issue.
600 // The best we can do is trying again. If it should still fail, Places
601 // won't work properly and will be handled as LOCKED.
602 rv = InitSchema(&databaseMigrated);
603 if (NS_FAILED(rv)) {
604 rv = NS_ERROR_FILE_IS_LOCKED;
606 } else {
607 rv = NS_ERROR_FILE_CORRUPTED;
611 if (NS_WARN_IF(NS_FAILED(rv))) {
612 if (rv != NS_ERROR_FILE_IS_LOCKED) {
613 mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
615 // Some errors may not indicate a database corruption, for those cases we
616 // just bail out without throwing away a possibly valid places.sqlite.
617 if (rv == NS_ERROR_FILE_CORRUPTED) {
618 // Since we don't know which database is corrupt, we must replace both.
619 rv = BackupAndReplaceDatabaseFile(storage, DATABASE_FAVICONS_FILENAME,
620 false, false);
621 NS_ENSURE_SUCCESS(rv, rv);
622 rv = BackupAndReplaceDatabaseFile(storage, DATABASE_FILENAME,
623 shouldTryToCloneDb, true);
624 NS_ENSURE_SUCCESS(rv, rv);
625 // Try to initialize the new database again.
626 rv = SetupDatabaseConnection(storage);
627 NS_ENSURE_SUCCESS(rv, rv);
628 rv = InitSchema(&databaseMigrated);
630 // Bail out if we couldn't fix the database.
631 NS_ENSURE_SUCCESS(rv, rv);
634 if (databaseMigrated) {
635 mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_UPGRADED;
638 // Initialize here all the items that are not part of the on-disk database,
639 // like views, temp triggers or temp tables. The database should not be
640 // considered corrupt if any of the following fails.
642 rv = InitTempEntities();
643 NS_ENSURE_SUCCESS(rv, rv);
645 rv = CheckRoots();
646 NS_ENSURE_SUCCESS(rv, rv);
648 initSucceeded = true;
650 return NS_OK;
653 nsresult Database::NotifyConnectionInitalized() {
654 MOZ_ASSERT(NS_IsMainThread());
655 // Notify about Places initialization.
656 nsCOMArray<nsIObserver> entries;
657 mCacheObservers.GetEntries(entries);
658 for (int32_t idx = 0; idx < entries.Count(); ++idx) {
659 MOZ_ALWAYS_SUCCEEDS(
660 entries[idx]->Observe(nullptr, TOPIC_PLACES_INIT_COMPLETE, nullptr));
662 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
663 if (obs) {
664 MOZ_ALWAYS_SUCCEEDS(
665 obs->NotifyObservers(nullptr, TOPIC_PLACES_INIT_COMPLETE, nullptr));
667 return NS_OK;
670 nsresult Database::EnsureFaviconsDatabaseAttached(
671 const nsCOMPtr<mozIStorageService>& aStorage) {
672 MOZ_ASSERT(NS_IsMainThread());
674 nsCOMPtr<nsIFile> databaseFile;
675 NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
676 getter_AddRefs(databaseFile));
677 NS_ENSURE_STATE(databaseFile);
678 nsresult rv = databaseFile->Append(DATABASE_FAVICONS_FILENAME);
679 NS_ENSURE_SUCCESS(rv, rv);
680 nsString iconsPath;
681 rv = databaseFile->GetPath(iconsPath);
682 NS_ENSURE_SUCCESS(rv, rv);
684 bool fileExists = false;
685 if (NS_SUCCEEDED(databaseFile->Exists(&fileExists)) && fileExists) {
686 return AttachDatabase(mMainConn, NS_ConvertUTF16toUTF8(iconsPath),
687 "favicons"_ns);
690 // Open the database file, this will also create it.
691 nsCOMPtr<mozIStorageConnection> conn;
692 rv = aStorage->OpenUnsharedDatabase(databaseFile,
693 mozIStorageService::CONNECTION_DEFAULT,
694 getter_AddRefs(conn));
695 NS_ENSURE_SUCCESS(rv, rv);
698 // Ensure we'll close the connection when done.
699 auto cleanup = MakeScopeExit([&]() {
700 // We cannot use AsyncClose() here, because by the time we try to ATTACH
701 // this database, its transaction could be still be running and that would
702 // cause the ATTACH query to fail.
703 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(conn->Close()));
706 // Enable incremental vacuum for this database. Since it will contain even
707 // large blobs and can be cleared with history, it's worth to have it.
708 // Note that it will be necessary to manually use PRAGMA incremental_vacuum.
709 rv = conn->ExecuteSimpleSQL("PRAGMA auto_vacuum = INCREMENTAL"_ns);
710 NS_ENSURE_SUCCESS(rv, rv);
712 #if !defined(HAVE_64BIT_BUILD)
713 // Ensure that temp tables are held in memory, not on disk, on 32 bit
714 // platforms.
715 rv = conn->ExecuteSimpleSQL("PRAGMA temp_store = MEMORY"_ns);
716 NS_ENSURE_SUCCESS(rv, rv);
717 #endif
719 int32_t defaultPageSize;
720 rv = conn->GetDefaultPageSize(&defaultPageSize);
721 NS_ENSURE_SUCCESS(rv, rv);
722 rv = SetupDurability(conn, defaultPageSize);
723 NS_ENSURE_SUCCESS(rv, rv);
725 // We are going to update the database, so everything from now on should be
726 // in a transaction for performances.
727 mozStorageTransaction transaction(conn, false);
728 // XXX Handle the error, bug 1696133.
729 Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
730 rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS);
731 NS_ENSURE_SUCCESS(rv, rv);
732 rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ICONS_ICONURLHASH);
733 NS_ENSURE_SUCCESS(rv, rv);
734 rv = conn->ExecuteSimpleSQL(CREATE_MOZ_PAGES_W_ICONS);
735 NS_ENSURE_SUCCESS(rv, rv);
736 rv = conn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PAGES_W_ICONS_ICONURLHASH);
737 NS_ENSURE_SUCCESS(rv, rv);
738 rv = conn->ExecuteSimpleSQL(CREATE_MOZ_ICONS_TO_PAGES);
739 NS_ENSURE_SUCCESS(rv, rv);
740 rv = transaction.Commit();
741 NS_ENSURE_SUCCESS(rv, rv);
743 // The scope exit will take care of closing the connection.
746 rv = AttachDatabase(mMainConn, NS_ConvertUTF16toUTF8(iconsPath),
747 "favicons"_ns);
748 NS_ENSURE_SUCCESS(rv, rv);
750 return NS_OK;
753 nsresult Database::BackupAndReplaceDatabaseFile(
754 nsCOMPtr<mozIStorageService>& aStorage, const nsString& aDbFilename,
755 bool aTryToClone, bool aReopenConnection) {
756 MOZ_ASSERT(NS_IsMainThread());
758 if (aDbFilename.Equals(DATABASE_FILENAME)) {
759 mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT;
760 } else {
761 // Due to OS file lockings, attached databases can't be cloned properly,
762 // otherwise trying to reattach them later would fail.
763 aTryToClone = false;
766 nsCOMPtr<nsIFile> profDir;
767 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
768 getter_AddRefs(profDir));
769 NS_ENSURE_SUCCESS(rv, rv);
770 nsCOMPtr<nsIFile> databaseFile;
771 rv = profDir->Clone(getter_AddRefs(databaseFile));
772 NS_ENSURE_SUCCESS(rv, rv);
773 rv = databaseFile->Append(aDbFilename);
774 NS_ENSURE_SUCCESS(rv, rv);
776 // If we already failed in the last 24 hours avoid to create another corrupt
777 // file, since doing so, in some situation, could cause us to create a new
778 // corrupt file at every try to access any Places service. That is bad
779 // because it would quickly fill the user's disk space without any notice.
780 nsCOMPtr<nsIFile> corruptFile;
781 rv = profDir->Clone(getter_AddRefs(corruptFile));
782 NS_ENSURE_SUCCESS(rv, rv);
783 nsString corruptFilename = getCorruptFilename(aDbFilename);
784 rv = corruptFile->Append(corruptFilename);
785 NS_ENSURE_SUCCESS(rv, rv);
786 if (!isRecentCorruptFile(corruptFile)) {
787 // Ensure we never create more than one corrupt file.
788 nsCOMPtr<nsIFile> corruptFile;
789 rv = profDir->Clone(getter_AddRefs(corruptFile));
790 NS_ENSURE_SUCCESS(rv, rv);
791 nsString corruptFilename = getCorruptFilename(aDbFilename);
792 rv = corruptFile->Append(corruptFilename);
793 NS_ENSURE_SUCCESS(rv, rv);
794 rv = corruptFile->Remove(false);
795 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
796 return rv;
799 nsCOMPtr<nsIFile> backup;
800 Unused << aStorage->BackupDatabaseFile(databaseFile, corruptFilename,
801 profDir, getter_AddRefs(backup));
804 // If anything fails from this point on, we have a stale connection or
805 // database file, and there's not much more we can do.
806 // The only thing we can try to do is to replace the database on the next
807 // startup, and report the problem through telemetry.
809 enum eCorruptDBReplaceStage : int8_t {
810 stage_closing = 0,
811 stage_removing,
812 stage_reopening,
813 stage_replaced,
814 stage_cloning,
815 stage_cloned
817 eCorruptDBReplaceStage stage = stage_closing;
818 auto guard = MakeScopeExit([&]() {
819 // In case we failed to close the connection or remove the database file,
820 // we want to try again at the next startup.
821 if (stage == stage_closing || stage == stage_removing) {
822 Preferences::SetString(PREF_FORCE_DATABASE_REPLACEMENT, aDbFilename);
824 // Report the corruption through telemetry.
825 Telemetry::Accumulate(
826 Telemetry::PLACES_DATABASE_CORRUPTION_HANDLING_STAGE,
827 static_cast<int8_t>(stage));
830 // Close database connection if open.
831 if (mMainConn) {
832 rv = mMainConn->SpinningSynchronousClose();
833 NS_ENSURE_SUCCESS(rv, rv);
834 mMainConn = nullptr;
837 // Remove the broken database.
838 stage = stage_removing;
839 rv = databaseFile->Remove(false);
840 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
841 return rv;
844 // Create a new database file and try to clone tables from the corrupt one.
845 bool cloned = false;
846 if (aTryToClone &&
847 Preferences::GetBool(PREF_DATABASE_CLONEONCORRUPTION, true)) {
848 stage = stage_cloning;
849 rv = TryToCloneTablesFromCorruptDatabase(aStorage, databaseFile);
850 if (NS_SUCCEEDED(rv)) {
851 // If we cloned successfully, we should not consider the database
852 // corrupt anymore, otherwise we could reimport default bookmarks.
853 mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_OK;
854 cloned = true;
858 if (aReopenConnection) {
859 // Use an unshared connection, it will consume more memory but avoid
860 // shared cache contentions across threads.
861 stage = stage_reopening;
862 rv = aStorage->OpenUnsharedDatabase(
863 databaseFile, mozIStorageService::CONNECTION_DEFAULT,
864 getter_AddRefs(mMainConn));
865 NS_ENSURE_SUCCESS(rv, rv);
868 stage = cloned ? stage_cloned : stage_replaced;
871 return NS_OK;
874 nsresult Database::TryToCloneTablesFromCorruptDatabase(
875 const nsCOMPtr<mozIStorageService>& aStorage,
876 const nsCOMPtr<nsIFile>& aDatabaseFile) {
877 MOZ_ASSERT(NS_IsMainThread());
879 nsAutoString filename;
880 nsresult rv = aDatabaseFile->GetLeafName(filename);
882 nsCOMPtr<nsIFile> corruptFile;
883 rv = aDatabaseFile->Clone(getter_AddRefs(corruptFile));
884 NS_ENSURE_SUCCESS(rv, rv);
885 rv = corruptFile->SetLeafName(getCorruptFilename(filename));
886 NS_ENSURE_SUCCESS(rv, rv);
887 nsAutoString path;
888 rv = corruptFile->GetPath(path);
889 NS_ENSURE_SUCCESS(rv, rv);
891 nsCOMPtr<nsIFile> recoverFile;
892 rv = aDatabaseFile->Clone(getter_AddRefs(recoverFile));
893 NS_ENSURE_SUCCESS(rv, rv);
894 rv = recoverFile->SetLeafName(getRecoverFilename(filename));
895 NS_ENSURE_SUCCESS(rv, rv);
896 // Ensure there's no previous recover file.
897 rv = recoverFile->Remove(false);
898 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
899 return rv;
902 nsCOMPtr<mozIStorageConnection> conn;
903 auto guard = MakeScopeExit([&]() {
904 if (conn) {
905 Unused << conn->Close();
907 RemoveFileSwallowsErrors(recoverFile);
910 rv = aStorage->OpenUnsharedDatabase(recoverFile,
911 mozIStorageService::CONNECTION_DEFAULT,
912 getter_AddRefs(conn));
913 NS_ENSURE_SUCCESS(rv, rv);
914 rv = AttachDatabase(conn, NS_ConvertUTF16toUTF8(path), "corrupt"_ns);
915 NS_ENSURE_SUCCESS(rv, rv);
917 mozStorageTransaction transaction(conn, false);
919 // XXX Handle the error, bug 1696133.
920 Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
922 // Copy the schema version.
923 nsCOMPtr<mozIStorageStatement> stmt;
924 (void)conn->CreateStatement("PRAGMA corrupt.user_version"_ns,
925 getter_AddRefs(stmt));
926 NS_ENSURE_TRUE(stmt, NS_ERROR_OUT_OF_MEMORY);
927 bool hasResult;
928 rv = stmt->ExecuteStep(&hasResult);
929 NS_ENSURE_SUCCESS(rv, rv);
930 int32_t schemaVersion = stmt->AsInt32(0);
931 rv = conn->SetSchemaVersion(schemaVersion);
932 NS_ENSURE_SUCCESS(rv, rv);
934 // Recreate the tables.
935 rv = conn->CreateStatement(
936 nsLiteralCString(
937 "SELECT name, sql FROM corrupt.sqlite_master "
938 "WHERE type = 'table' AND name BETWEEN 'moz_' AND 'moza'"),
939 getter_AddRefs(stmt));
940 NS_ENSURE_SUCCESS(rv, rv);
941 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
942 nsAutoCString name;
943 rv = stmt->GetUTF8String(0, name);
944 NS_ENSURE_SUCCESS(rv, rv);
945 nsAutoCString query;
946 rv = stmt->GetUTF8String(1, query);
947 NS_ENSURE_SUCCESS(rv, rv);
948 rv = conn->ExecuteSimpleSQL(query);
949 NS_ENSURE_SUCCESS(rv, rv);
950 // Copy the table contents.
951 rv = conn->ExecuteSimpleSQL("INSERT INTO main."_ns + name +
952 " SELECT * FROM corrupt."_ns + name);
953 if (NS_FAILED(rv)) {
954 rv = conn->ExecuteSimpleSQL("INSERT INTO main."_ns + name +
955 " SELECT * FROM corrupt."_ns + name +
956 " ORDER BY rowid DESC"_ns);
958 NS_ENSURE_SUCCESS(rv, rv);
961 // Recreate the indices. Doing this after data addition is faster.
962 rv = conn->CreateStatement(
963 nsLiteralCString(
964 "SELECT sql FROM corrupt.sqlite_master "
965 "WHERE type <> 'table' AND name BETWEEN 'moz_' AND 'moza'"),
966 getter_AddRefs(stmt));
967 NS_ENSURE_SUCCESS(rv, rv);
968 hasResult = false;
969 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
970 nsAutoCString query;
971 rv = stmt->GetUTF8String(0, query);
972 NS_ENSURE_SUCCESS(rv, rv);
973 rv = conn->ExecuteSimpleSQL(query);
974 NS_ENSURE_SUCCESS(rv, rv);
976 rv = stmt->Finalize();
977 NS_ENSURE_SUCCESS(rv, rv);
979 rv = transaction.Commit();
980 NS_ENSURE_SUCCESS(rv, rv);
982 MOZ_ALWAYS_SUCCEEDS(conn->Close());
983 conn = nullptr;
984 rv = recoverFile->RenameTo(nullptr, filename);
985 NS_ENSURE_SUCCESS(rv, rv);
987 RemoveFileSwallowsErrors(corruptFile);
988 RemoveFileSwallowsErrors(corruptFile, u"-wal"_ns);
989 RemoveFileSwallowsErrors(corruptFile, u"-shm"_ns);
991 guard.release();
992 return NS_OK;
995 nsresult Database::SetupDatabaseConnection(
996 nsCOMPtr<mozIStorageService>& aStorage) {
997 MOZ_ASSERT(NS_IsMainThread());
999 // Using immediate transactions allows the main connection to retry writes
1000 // that fail with `SQLITE_BUSY` because a cloned connection has locked the
1001 // database for writing.
1002 nsresult rv = mMainConn->SetDefaultTransactionType(
1003 mozIStorageConnection::TRANSACTION_IMMEDIATE);
1004 NS_ENSURE_SUCCESS(rv, rv);
1006 // WARNING: any statement executed before setting the journal mode must be
1007 // finalized, since SQLite doesn't allow changing the journal mode if there
1008 // is any outstanding statement.
1011 // Get the page size. This may be different than the default if the
1012 // database file already existed with a different page size.
1013 nsCOMPtr<mozIStorageStatement> statement;
1014 rv = mMainConn->CreateStatement(
1015 nsLiteralCString(MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"),
1016 getter_AddRefs(statement));
1017 NS_ENSURE_SUCCESS(rv, rv);
1018 bool hasResult = false;
1019 rv = statement->ExecuteStep(&hasResult);
1020 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FILE_CORRUPTED);
1021 rv = statement->GetInt32(0, &mDBPageSize);
1022 NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && mDBPageSize > 0,
1023 NS_ERROR_FILE_CORRUPTED);
1026 #if !defined(HAVE_64BIT_BUILD)
1027 // Ensure that temp tables are held in memory, not on disk, on 32 bit
1028 // platforms.
1029 rv = mMainConn->ExecuteSimpleSQL(nsLiteralCString(
1030 MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA temp_store = MEMORY"));
1031 NS_ENSURE_SUCCESS(rv, rv);
1032 #endif
1034 rv = SetupDurability(mMainConn, mDBPageSize);
1035 NS_ENSURE_SUCCESS(rv, rv);
1037 nsAutoCString busyTimeoutPragma("PRAGMA busy_timeout = ");
1038 busyTimeoutPragma.AppendInt(DATABASE_BUSY_TIMEOUT_MS);
1039 (void)mMainConn->ExecuteSimpleSQL(busyTimeoutPragma);
1041 // Enable FOREIGN KEY support. This is a strict requirement.
1042 rv = mMainConn->ExecuteSimpleSQL(nsLiteralCString(
1043 MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA foreign_keys = ON"));
1044 NS_ENSURE_SUCCESS(rv, NS_ERROR_FILE_CORRUPTED);
1045 #ifdef DEBUG
1047 // There are a few cases where setting foreign_keys doesn't work:
1048 // * in the middle of a multi-statement transaction
1049 // * if the SQLite library in use doesn't support them
1050 // Since we need foreign_keys, let's at least assert in debug mode.
1051 nsCOMPtr<mozIStorageStatement> stmt;
1052 mMainConn->CreateStatement("PRAGMA foreign_keys"_ns, getter_AddRefs(stmt));
1053 bool hasResult = false;
1054 if (stmt && NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1055 int32_t fkState = stmt->AsInt32(0);
1056 MOZ_ASSERT(fkState, "Foreign keys should be enabled");
1059 #endif
1061 // Attach the favicons database to the main connection.
1062 rv = EnsureFaviconsDatabaseAttached(aStorage);
1063 if (NS_FAILED(rv)) {
1064 // The favicons database may be corrupt. Try to replace and reattach it.
1065 nsCOMPtr<nsIFile> iconsFile;
1066 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
1067 getter_AddRefs(iconsFile));
1068 NS_ENSURE_SUCCESS(rv, rv);
1069 rv = iconsFile->Append(DATABASE_FAVICONS_FILENAME);
1070 NS_ENSURE_SUCCESS(rv, rv);
1071 rv = iconsFile->Remove(false);
1072 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
1073 return rv;
1075 rv = EnsureFaviconsDatabaseAttached(aStorage);
1076 NS_ENSURE_SUCCESS(rv, rv);
1079 // Create favicons temp entities.
1080 rv = mMainConn->ExecuteSimpleSQL(CREATE_ICONS_AFTERINSERT_TRIGGER);
1081 NS_ENSURE_SUCCESS(rv, rv);
1083 // We use our functions during migration, so initialize them now.
1084 rv = InitFunctions();
1085 NS_ENSURE_SUCCESS(rv, rv);
1087 return NS_OK;
1090 nsresult Database::InitSchema(bool* aDatabaseMigrated) {
1091 MOZ_ASSERT(NS_IsMainThread());
1092 *aDatabaseMigrated = false;
1094 // Get the database schema version.
1095 int32_t currentSchemaVersion;
1096 nsresult rv = mMainConn->GetSchemaVersion(&currentSchemaVersion);
1097 NS_ENSURE_SUCCESS(rv, rv);
1098 bool databaseInitialized = currentSchemaVersion > 0;
1100 if (databaseInitialized &&
1101 currentSchemaVersion == nsINavHistoryService::DATABASE_SCHEMA_VERSION) {
1102 // The database is up to date and ready to go.
1103 return NS_OK;
1106 // We are going to update the database, so everything from now on should be in
1107 // a transaction for performances.
1108 mozStorageTransaction transaction(mMainConn, false);
1110 // XXX Handle the error, bug 1696133.
1111 Unused << NS_WARN_IF(NS_FAILED(transaction.Start()));
1113 if (databaseInitialized) {
1114 // Migration How-to:
1116 // 1. increment PLACES_SCHEMA_VERSION.
1117 // 2. implement a method that performs upgrade to your version from the
1118 // previous one.
1120 // NOTE: The downgrade process is pretty much complicated by the fact old
1121 // versions cannot know what a new version is going to implement.
1122 // The only thing we will do for downgrades is setting back the schema
1123 // version, so that next upgrades will run again the migration step.
1125 if (currentSchemaVersion < nsINavHistoryService::DATABASE_SCHEMA_VERSION) {
1126 *aDatabaseMigrated = true;
1128 if (currentSchemaVersion < 52) {
1129 // These are versions older than Firefox 68 ESR that are not supported
1130 // anymore. In this case it's safer to just replace the database.
1131 return NS_ERROR_FILE_CORRUPTED;
1134 // Firefox 62 uses schema version 52.
1135 // Firefox 68 uses schema version 52. - This is an ESR.
1137 if (currentSchemaVersion < 53) {
1138 rv = MigrateV53Up();
1139 NS_ENSURE_SUCCESS(rv, rv);
1142 // Firefox 69 uses schema version 53
1143 // Firefox 72 is a watershed release.
1145 if (currentSchemaVersion < 54) {
1146 rv = MigrateV54Up();
1147 NS_ENSURE_SUCCESS(rv, rv);
1150 // Firefox 81 uses schema version 54
1152 if (currentSchemaVersion < 55) {
1153 rv = MigrateV55Up();
1154 NS_ENSURE_SUCCESS(rv, rv);
1157 if (currentSchemaVersion < 56) {
1158 rv = MigrateV56Up();
1159 NS_ENSURE_SUCCESS(rv, rv);
1162 if (currentSchemaVersion < 57) {
1163 rv = MigrateV57Up();
1164 NS_ENSURE_SUCCESS(rv, rv);
1167 // Firefox 91 uses schema version 57
1169 // The schema 58 migration is no longer needed.
1171 // Firefox 92 uses schema version 58
1173 // The schema 59 migration is no longer needed.
1175 // Firefox 94 uses schema version 59
1177 if (currentSchemaVersion < 60) {
1178 rv = MigrateV60Up();
1179 NS_ENSURE_SUCCESS(rv, rv);
1182 // Firefox 96 uses schema version 60
1184 if (currentSchemaVersion < 61) {
1185 rv = MigrateV61Up();
1186 NS_ENSURE_SUCCESS(rv, rv);
1189 // The schema 62 migration is no longer needed.
1191 // Firefox 97 uses schema version 62
1193 // The schema 63 migration is no longer needed.
1195 // Firefox 98 uses schema version 63
1197 // The schema 64 migration is no longer needed.
1199 // Firefox 99 uses schema version 64
1201 // The schema 65 migration is no longer needed.
1203 // The schema 66 migration is no longer needed.
1205 // Firefox 100 uses schema version 66
1207 if (currentSchemaVersion < 67) {
1208 rv = MigrateV67Up();
1209 NS_ENSURE_SUCCESS(rv, rv);
1212 // The schema 68 migration is no longer needed.
1214 // Firefox 103 uses schema version 68
1216 if (currentSchemaVersion < 69) {
1217 rv = MigrateV69Up();
1218 NS_ENSURE_SUCCESS(rv, rv);
1221 // Firefox 104 uses schema version 69
1223 if (currentSchemaVersion < 70) {
1224 rv = MigrateV70Up();
1225 NS_ENSURE_SUCCESS(rv, rv);
1228 if (currentSchemaVersion < 71) {
1229 rv = MigrateV71Up();
1230 NS_ENSURE_SUCCESS(rv, rv);
1233 // Firefox 110 uses schema version 71
1235 if (currentSchemaVersion < 72) {
1236 rv = MigrateV72Up();
1237 NS_ENSURE_SUCCESS(rv, rv);
1240 // Firefox 111 uses schema version 72
1242 if (currentSchemaVersion < 73) {
1243 rv = MigrateV73Up();
1244 NS_ENSURE_SUCCESS(rv, rv);
1247 // Firefox 114 uses schema version 73
1249 if (currentSchemaVersion < 74) {
1250 rv = MigrateV74Up();
1251 NS_ENSURE_SUCCESS(rv, rv);
1254 // Firefox 115 uses schema version 74
1256 if (currentSchemaVersion < 75) {
1257 rv = MigrateV75Up();
1258 NS_ENSURE_SUCCESS(rv, rv);
1261 // Firefox 118 uses schema version 75
1263 // Schema Upgrades must add migration code here.
1264 // >>> IMPORTANT! <<<
1265 // NEVER MIX UP SYNC AND ASYNC EXECUTION IN MIGRATORS, YOU MAY LOCK THE
1266 // CONNECTION AND CAUSE FURTHER STEPS TO FAIL.
1267 // In case, set a bool and do the async work in the ScopeExit guard just
1268 // before the migration steps.
1270 } else {
1271 // This is a new database, so we have to create all the tables and indices.
1273 // moz_origins.
1274 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ORIGINS);
1275 NS_ENSURE_SUCCESS(rv, rv);
1277 // moz_places.
1278 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES);
1279 NS_ENSURE_SUCCESS(rv, rv);
1280 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES_EXTRA);
1281 NS_ENSURE_SUCCESS(rv, rv);
1282 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL_HASH);
1283 NS_ENSURE_SUCCESS(rv, rv);
1284 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_REVHOST);
1285 NS_ENSURE_SUCCESS(rv, rv);
1286 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_VISITCOUNT);
1287 NS_ENSURE_SUCCESS(rv, rv);
1288 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FRECENCY);
1289 NS_ENSURE_SUCCESS(rv, rv);
1290 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_LASTVISITDATE);
1291 NS_ENSURE_SUCCESS(rv, rv);
1292 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID);
1293 NS_ENSURE_SUCCESS(rv, rv);
1294 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_ORIGIN_ID);
1295 NS_ENSURE_SUCCESS(rv, rv);
1296 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_ALT_FRECENCY);
1297 NS_ENSURE_SUCCESS(rv, rv);
1299 // moz_historyvisits.
1300 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS);
1301 NS_ENSURE_SUCCESS(rv, rv);
1302 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS_EXTRA);
1303 NS_ENSURE_SUCCESS(rv, rv);
1304 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE);
1305 NS_ENSURE_SUCCESS(rv, rv);
1306 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_FROMVISIT);
1307 NS_ENSURE_SUCCESS(rv, rv);
1308 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_VISITDATE);
1309 NS_ENSURE_SUCCESS(rv, rv);
1311 // moz_inputhistory.
1312 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY);
1313 NS_ENSURE_SUCCESS(rv, rv);
1315 // moz_bookmarks.
1316 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS);
1317 NS_ENSURE_SUCCESS(rv, rv);
1318 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS_DELETED);
1319 NS_ENSURE_SUCCESS(rv, rv);
1320 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACETYPE);
1321 NS_ENSURE_SUCCESS(rv, rv);
1322 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PARENTPOSITION);
1323 NS_ENSURE_SUCCESS(rv, rv);
1324 rv =
1325 mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED);
1326 NS_ENSURE_SUCCESS(rv, rv);
1327 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_DATEADDED);
1328 NS_ENSURE_SUCCESS(rv, rv);
1329 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID);
1330 NS_ENSURE_SUCCESS(rv, rv);
1332 // moz_keywords.
1333 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_KEYWORDS);
1334 NS_ENSURE_SUCCESS(rv, rv);
1335 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA);
1336 NS_ENSURE_SUCCESS(rv, rv);
1338 // moz_anno_attributes.
1339 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNO_ATTRIBUTES);
1340 NS_ENSURE_SUCCESS(rv, rv);
1342 // moz_annos.
1343 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNOS);
1344 NS_ENSURE_SUCCESS(rv, rv);
1345 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE);
1346 NS_ENSURE_SUCCESS(rv, rv);
1348 // moz_items_annos.
1349 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ITEMS_ANNOS);
1350 NS_ENSURE_SUCCESS(rv, rv);
1351 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE);
1352 NS_ENSURE_SUCCESS(rv, rv);
1354 // moz_meta.
1355 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_META);
1356 NS_ENSURE_SUCCESS(rv, rv);
1358 // moz_places_metadata
1359 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES_METADATA);
1360 NS_ENSURE_SUCCESS(rv, rv);
1361 rv = mMainConn->ExecuteSimpleSQL(
1362 CREATE_IDX_MOZ_PLACES_METADATA_PLACECREATED);
1363 NS_ENSURE_SUCCESS(rv, rv);
1364 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_METADATA_REFERRER);
1365 NS_ENSURE_SUCCESS(rv, rv);
1367 // moz_places_metadata_search_queries
1368 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES_METADATA_SEARCH_QUERIES);
1369 NS_ENSURE_SUCCESS(rv, rv);
1371 // moz_previews_tombstones
1372 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PREVIEWS_TOMBSTONES);
1373 NS_ENSURE_SUCCESS(rv, rv);
1375 // The bookmarks roots get initialized in CheckRoots().
1378 // Set the schema version to the current one.
1379 rv = mMainConn->SetSchemaVersion(
1380 nsINavHistoryService::DATABASE_SCHEMA_VERSION);
1381 NS_ENSURE_SUCCESS(rv, rv);
1383 rv = transaction.Commit();
1384 NS_ENSURE_SUCCESS(rv, rv);
1386 // ANY FAILURE IN THIS METHOD WILL CAUSE US TO MARK THE DATABASE AS CORRUPT
1387 // AND TRY TO REPLACE IT.
1388 // DO NOT PUT HERE ANYTHING THAT IS NOT RELATED TO INITIALIZATION OR MODIFYING
1389 // THE DISK DATABASE.
1391 return NS_OK;
1394 nsresult Database::CheckRoots() {
1395 MOZ_ASSERT(NS_IsMainThread());
1397 // If the database has just been created, skip straight to the part where
1398 // we create the roots.
1399 if (mDatabaseStatus == nsINavHistoryService::DATABASE_STATUS_CREATE) {
1400 return EnsureBookmarkRoots(0, /* shouldReparentRoots */ false);
1403 nsCOMPtr<mozIStorageStatement> stmt;
1404 nsresult rv = mMainConn->CreateStatement(
1405 nsLiteralCString("SELECT guid, id, position, parent FROM moz_bookmarks "
1406 "WHERE guid IN ( "
1407 "'" ROOT_GUID "', '" MENU_ROOT_GUID
1408 "', '" TOOLBAR_ROOT_GUID "', "
1409 "'" TAGS_ROOT_GUID "', '" UNFILED_ROOT_GUID
1410 "', '" MOBILE_ROOT_GUID "' )"),
1411 getter_AddRefs(stmt));
1412 NS_ENSURE_SUCCESS(rv, rv);
1414 bool hasResult;
1415 nsAutoCString guid;
1416 int32_t maxPosition = 0;
1417 bool shouldReparentRoots = false;
1418 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1419 rv = stmt->GetUTF8String(0, guid);
1420 NS_ENSURE_SUCCESS(rv, rv);
1422 int64_t parentId = stmt->AsInt64(3);
1424 if (guid.EqualsLiteral(ROOT_GUID)) {
1425 mRootId = stmt->AsInt64(1);
1426 shouldReparentRoots |= parentId != 0;
1427 } else {
1428 maxPosition = std::max(stmt->AsInt32(2), maxPosition);
1430 if (guid.EqualsLiteral(MENU_ROOT_GUID)) {
1431 mMenuRootId = stmt->AsInt64(1);
1432 } else if (guid.EqualsLiteral(TOOLBAR_ROOT_GUID)) {
1433 mToolbarRootId = stmt->AsInt64(1);
1434 } else if (guid.EqualsLiteral(TAGS_ROOT_GUID)) {
1435 mTagsRootId = stmt->AsInt64(1);
1436 } else if (guid.EqualsLiteral(UNFILED_ROOT_GUID)) {
1437 mUnfiledRootId = stmt->AsInt64(1);
1438 } else if (guid.EqualsLiteral(MOBILE_ROOT_GUID)) {
1439 mMobileRootId = stmt->AsInt64(1);
1441 shouldReparentRoots |= parentId != mRootId;
1445 rv = EnsureBookmarkRoots(maxPosition + 1, shouldReparentRoots);
1446 NS_ENSURE_SUCCESS(rv, rv);
1448 return NS_OK;
1451 nsresult Database::EnsureBookmarkRoots(const int32_t startPosition,
1452 bool shouldReparentRoots) {
1453 MOZ_ASSERT(NS_IsMainThread());
1455 nsresult rv;
1457 if (mRootId < 1) {
1458 // The first root's title is an empty string.
1459 rv = CreateRoot(mMainConn, "places"_ns, "root________"_ns, ""_ns, 0,
1460 mRootId);
1462 if (NS_FAILED(rv)) return rv;
1465 int32_t position = startPosition;
1467 // For the other roots, the UI doesn't rely on the value in the database, so
1468 // just set it to something simple to make it easier for humans to read.
1469 if (mMenuRootId < 1) {
1470 rv = CreateRoot(mMainConn, "menu"_ns, "menu________"_ns, "menu"_ns,
1471 position, mMenuRootId);
1472 if (NS_FAILED(rv)) return rv;
1473 position++;
1476 if (mToolbarRootId < 1) {
1477 rv = CreateRoot(mMainConn, "toolbar"_ns, "toolbar_____"_ns, "toolbar"_ns,
1478 position, mToolbarRootId);
1479 if (NS_FAILED(rv)) return rv;
1480 position++;
1483 if (mTagsRootId < 1) {
1484 rv = CreateRoot(mMainConn, "tags"_ns, "tags________"_ns, "tags"_ns,
1485 position, mTagsRootId);
1486 if (NS_FAILED(rv)) return rv;
1487 position++;
1490 if (mUnfiledRootId < 1) {
1491 rv = CreateRoot(mMainConn, "unfiled"_ns, "unfiled_____"_ns, "unfiled"_ns,
1492 position, mUnfiledRootId);
1493 if (NS_FAILED(rv)) return rv;
1494 position++;
1497 if (mMobileRootId < 1) {
1498 int64_t mobileRootId = CreateMobileRoot();
1499 if (mobileRootId <= 0) return NS_ERROR_FAILURE;
1501 nsCOMPtr<mozIStorageStatement> mobileRootSyncStatusStmt;
1502 rv = mMainConn->CreateStatement(
1503 nsLiteralCString("UPDATE moz_bookmarks SET syncStatus = "
1504 ":sync_status WHERE id = :id"),
1505 getter_AddRefs(mobileRootSyncStatusStmt));
1506 if (NS_FAILED(rv)) return rv;
1508 rv = mobileRootSyncStatusStmt->BindInt32ByName(
1509 "sync_status"_ns, nsINavBookmarksService::SYNC_STATUS_NEW);
1510 if (NS_FAILED(rv)) return rv;
1511 rv = mobileRootSyncStatusStmt->BindInt64ByName("id"_ns, mobileRootId);
1512 if (NS_FAILED(rv)) return rv;
1514 rv = mobileRootSyncStatusStmt->Execute();
1515 if (NS_FAILED(rv)) return rv;
1517 mMobileRootId = mobileRootId;
1521 if (!shouldReparentRoots) {
1522 return NS_OK;
1525 // At least one root had the wrong parent, so we need to ensure that
1526 // all roots are parented correctly, fix their positions, and bump the
1527 // Sync change counter.
1528 rv = mMainConn->ExecuteSimpleSQL(nsLiteralCString(
1529 "CREATE TEMP TRIGGER moz_ensure_bookmark_roots_trigger "
1530 "AFTER UPDATE OF parent ON moz_bookmarks FOR EACH ROW "
1531 "WHEN OLD.parent <> NEW.parent "
1532 "BEGIN "
1533 "UPDATE moz_bookmarks SET "
1534 "syncChangeCounter = syncChangeCounter + 1 "
1535 "WHERE id IN (OLD.parent, NEW.parent, NEW.id); "
1537 "UPDATE moz_bookmarks SET "
1538 "position = position - 1 "
1539 "WHERE parent = OLD.parent AND position >= OLD.position; "
1541 // Fix the positions of the root's old siblings. Since we've already
1542 // moved the root, we need to exclude it from the subquery.
1543 "UPDATE moz_bookmarks SET "
1544 "position = IFNULL((SELECT MAX(position) + 1 FROM moz_bookmarks "
1545 "WHERE parent = NEW.parent AND "
1546 "id <> NEW.id), 0)"
1547 "WHERE id = NEW.id; "
1548 "END"));
1549 if (NS_FAILED(rv)) return rv;
1550 auto guard = MakeScopeExit([&]() {
1551 Unused << mMainConn->ExecuteSimpleSQL(
1552 "DROP TRIGGER moz_ensure_bookmark_roots_trigger"_ns);
1555 nsCOMPtr<mozIStorageStatement> reparentStmt;
1556 rv = mMainConn->CreateStatement(
1557 nsLiteralCString(
1558 "UPDATE moz_bookmarks SET "
1559 "parent = CASE id WHEN :root_id THEN 0 ELSE :root_id END "
1560 "WHERE id IN (:root_id, :menu_root_id, :toolbar_root_id, "
1561 ":tags_root_id, "
1562 ":unfiled_root_id, :mobile_root_id)"),
1563 getter_AddRefs(reparentStmt));
1564 if (NS_FAILED(rv)) return rv;
1566 rv = reparentStmt->BindInt64ByName("root_id"_ns, mRootId);
1567 if (NS_FAILED(rv)) return rv;
1568 rv = reparentStmt->BindInt64ByName("menu_root_id"_ns, mMenuRootId);
1569 if (NS_FAILED(rv)) return rv;
1570 rv = reparentStmt->BindInt64ByName("toolbar_root_id"_ns, mToolbarRootId);
1571 if (NS_FAILED(rv)) return rv;
1572 rv = reparentStmt->BindInt64ByName("tags_root_id"_ns, mTagsRootId);
1573 if (NS_FAILED(rv)) return rv;
1574 rv = reparentStmt->BindInt64ByName("unfiled_root_id"_ns, mUnfiledRootId);
1575 if (NS_FAILED(rv)) return rv;
1576 rv = reparentStmt->BindInt64ByName("mobile_root_id"_ns, mMobileRootId);
1577 if (NS_FAILED(rv)) return rv;
1579 rv = reparentStmt->Execute();
1580 if (NS_FAILED(rv)) return rv;
1582 return NS_OK;
1585 nsresult Database::InitFunctions() {
1586 MOZ_ASSERT(NS_IsMainThread());
1588 nsresult rv = GetUnreversedHostFunction::create(mMainConn);
1589 NS_ENSURE_SUCCESS(rv, rv);
1590 rv = MatchAutoCompleteFunction::create(mMainConn);
1591 NS_ENSURE_SUCCESS(rv, rv);
1592 rv = CalculateFrecencyFunction::create(mMainConn);
1593 NS_ENSURE_SUCCESS(rv, rv);
1594 rv = GenerateGUIDFunction::create(mMainConn);
1595 NS_ENSURE_SUCCESS(rv, rv);
1596 rv = IsValidGUIDFunction::create(mMainConn);
1597 NS_ENSURE_SUCCESS(rv, rv);
1598 rv = FixupURLFunction::create(mMainConn);
1599 NS_ENSURE_SUCCESS(rv, rv);
1600 rv = StoreLastInsertedIdFunction::create(mMainConn);
1601 NS_ENSURE_SUCCESS(rv, rv);
1602 rv = HashFunction::create(mMainConn);
1603 NS_ENSURE_SUCCESS(rv, rv);
1604 rv = GetQueryParamFunction::create(mMainConn);
1605 NS_ENSURE_SUCCESS(rv, rv);
1606 rv = GetPrefixFunction::create(mMainConn);
1607 NS_ENSURE_SUCCESS(rv, rv);
1608 rv = GetHostAndPortFunction::create(mMainConn);
1609 NS_ENSURE_SUCCESS(rv, rv);
1610 rv = StripPrefixAndUserinfoFunction::create(mMainConn);
1611 NS_ENSURE_SUCCESS(rv, rv);
1612 rv = IsFrecencyDecayingFunction::create(mMainConn);
1613 NS_ENSURE_SUCCESS(rv, rv);
1614 rv = NoteSyncChangeFunction::create(mMainConn);
1615 NS_ENSURE_SUCCESS(rv, rv);
1616 rv = InvalidateDaysOfHistoryFunction::create(mMainConn);
1617 NS_ENSURE_SUCCESS(rv, rv);
1618 rv = MD5HexFunction::create(mMainConn);
1619 NS_ENSURE_SUCCESS(rv, rv);
1620 rv = SetShouldStartFrecencyRecalculationFunction::create(mMainConn);
1621 NS_ENSURE_SUCCESS(rv, rv);
1622 rv = TargetFolderGuidFunction::create(mMainConn);
1623 NS_ENSURE_SUCCESS(rv, rv);
1625 if (StaticPrefs::places_frecency_pages_alternative_featureGate_AtStartup()) {
1626 rv = CalculateAltFrecencyFunction::create(mMainConn);
1627 NS_ENSURE_SUCCESS(rv, rv);
1630 return NS_OK;
1633 nsresult Database::InitTempEntities() {
1634 MOZ_ASSERT(NS_IsMainThread());
1636 nsresult rv =
1637 mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER);
1638 NS_ENSURE_SUCCESS(rv, rv);
1639 rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER);
1640 NS_ENSURE_SUCCESS(rv, rv);
1642 rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERINSERT_TRIGGER);
1643 NS_ENSURE_SUCCESS(rv, rv);
1644 rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEORIGINSDELETE_TEMP);
1645 NS_ENSURE_SUCCESS(rv, rv);
1646 rv = mMainConn->ExecuteSimpleSQL(
1647 CREATE_UPDATEORIGINSDELETE_AFTERDELETE_TRIGGER);
1648 NS_ENSURE_SUCCESS(rv, rv);
1650 if (Preferences::GetBool(PREF_PREVIEWS_ENABLED, false)) {
1651 rv = mMainConn->ExecuteSimpleSQL(
1652 CREATE_PLACES_AFTERDELETE_WPREVIEWS_TRIGGER);
1653 NS_ENSURE_SUCCESS(rv, rv);
1654 } else {
1655 rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERDELETE_TRIGGER);
1656 NS_ENSURE_SUCCESS(rv, rv);
1659 rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER);
1660 NS_ENSURE_SUCCESS(rv, rv);
1661 rv = mMainConn->ExecuteSimpleSQL(
1662 CREATE_PLACES_AFTERUPDATE_RECALC_FRECENCY_TRIGGER);
1663 NS_ENSURE_SUCCESS(rv, rv);
1664 rv = mMainConn->ExecuteSimpleSQL(
1665 CREATE_ORIGINS_AFTERUPDATE_RECALC_FRECENCY_TRIGGER);
1666 NS_ENSURE_SUCCESS(rv, rv);
1667 rv = mMainConn->ExecuteSimpleSQL(CREATE_ORIGINS_AFTERUPDATE_FRECENCY_TRIGGER);
1668 NS_ENSURE_SUCCESS(rv, rv);
1670 rv = mMainConn->ExecuteSimpleSQL(
1671 CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
1672 NS_ENSURE_SUCCESS(rv, rv);
1673 rv = mMainConn->ExecuteSimpleSQL(
1674 CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
1675 NS_ENSURE_SUCCESS(rv, rv);
1676 rv = mMainConn->ExecuteSimpleSQL(
1677 CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
1678 NS_ENSURE_SUCCESS(rv, rv);
1680 rv = mMainConn->ExecuteSimpleSQL(
1681 CREATE_KEYWORDS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
1682 NS_ENSURE_SUCCESS(rv, rv);
1683 rv = mMainConn->ExecuteSimpleSQL(
1684 CREATE_KEYWORDS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
1685 NS_ENSURE_SUCCESS(rv, rv);
1686 rv = mMainConn->ExecuteSimpleSQL(
1687 CREATE_KEYWORDS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
1688 NS_ENSURE_SUCCESS(rv, rv);
1689 rv =
1690 mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_DELETED_AFTERINSERT_TRIGGER);
1691 NS_ENSURE_SUCCESS(rv, rv);
1692 rv =
1693 mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_DELETED_AFTERDELETE_TRIGGER);
1694 NS_ENSURE_SUCCESS(rv, rv);
1696 rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_METADATA_AFTERDELETE_TRIGGER);
1697 NS_ENSURE_SUCCESS(rv, rv);
1699 // Create triggers to remove rows with empty json
1700 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES_EXTRA_AFTERUPDATE_TRIGGER);
1701 NS_ENSURE_SUCCESS(rv, rv);
1702 rv =
1703 mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS_AFTERUPDATE_TRIGGER);
1704 NS_ENSURE_SUCCESS(rv, rv);
1706 return NS_OK;
1709 nsresult Database::MigrateV53Up() {
1710 nsCOMPtr<mozIStorageStatement> stmt;
1711 nsresult rv = mMainConn->CreateStatement("SELECT 1 FROM moz_items_annos"_ns,
1712 getter_AddRefs(stmt));
1713 if (NS_FAILED(rv)) {
1714 // Likely we removed the table.
1715 return NS_OK;
1718 // Remove all item annotations but SYNC_PARENT_ANNO.
1719 rv = mMainConn->CreateStatement(
1720 nsLiteralCString(
1721 "DELETE FROM moz_items_annos "
1722 "WHERE anno_attribute_id NOT IN ( "
1723 " SELECT id FROM moz_anno_attributes WHERE name = :anno_name "
1724 ") "),
1725 getter_AddRefs(stmt));
1726 NS_ENSURE_SUCCESS(rv, rv);
1727 rv = stmt->BindUTF8StringByName("anno_name"_ns,
1728 nsLiteralCString(SYNC_PARENT_ANNO));
1729 NS_ENSURE_SUCCESS(rv, rv);
1730 rv = stmt->Execute();
1731 NS_ENSURE_SUCCESS(rv, rv);
1733 rv = mMainConn->ExecuteSimpleSQL(nsLiteralCString(
1734 "DELETE FROM moz_anno_attributes WHERE id IN ( "
1735 " SELECT id FROM moz_anno_attributes "
1736 " EXCEPT "
1737 " SELECT DISTINCT anno_attribute_id FROM moz_annos "
1738 " EXCEPT "
1739 " SELECT DISTINCT anno_attribute_id FROM moz_items_annos "
1740 ")"));
1741 NS_ENSURE_SUCCESS(rv, rv);
1743 return NS_OK;
1746 nsresult Database::MigrateV54Up() {
1747 // Add an expiration column to moz_icons_to_pages.
1748 nsCOMPtr<mozIStorageStatement> stmt;
1749 nsresult rv = mMainConn->CreateStatement(
1750 "SELECT expire_ms FROM moz_icons_to_pages"_ns, getter_AddRefs(stmt));
1751 if (NS_FAILED(rv)) {
1752 rv = mMainConn->ExecuteSimpleSQL(
1753 "ALTER TABLE moz_icons_to_pages "
1754 "ADD COLUMN expire_ms INTEGER NOT NULL DEFAULT 0 "_ns);
1755 NS_ENSURE_SUCCESS(rv, rv);
1758 // Set all the zero-ed entries as expired today, they won't be removed until
1759 // the next related page load.
1760 rv = mMainConn->ExecuteSimpleSQL(
1761 "UPDATE moz_icons_to_pages "
1762 "SET expire_ms = strftime('%s','now','localtime','start "
1763 "of day','utc') * 1000 "
1764 "WHERE expire_ms = 0 "_ns);
1765 NS_ENSURE_SUCCESS(rv, rv);
1767 return NS_OK;
1770 nsresult Database::MigrateV55Up() {
1771 // Add places metadata tables.
1772 nsCOMPtr<mozIStorageStatement> stmt;
1773 nsresult rv = mMainConn->CreateStatement(
1774 "SELECT id FROM moz_places_metadata"_ns, getter_AddRefs(stmt));
1775 if (NS_FAILED(rv)) {
1776 // Create the tables.
1777 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES_METADATA);
1778 NS_ENSURE_SUCCESS(rv, rv);
1779 // moz_places_metadata_search_queries.
1780 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES_METADATA_SEARCH_QUERIES);
1781 NS_ENSURE_SUCCESS(rv, rv);
1784 return NS_OK;
1787 nsresult Database::MigrateV56Up() {
1788 // Add places metadata (place_id, created_at) index.
1789 return mMainConn->ExecuteSimpleSQL(
1790 CREATE_IDX_MOZ_PLACES_METADATA_PLACECREATED);
1793 nsresult Database::MigrateV57Up() {
1794 // Add the scrolling columns to the metadata.
1795 nsCOMPtr<mozIStorageStatement> stmt;
1796 nsresult rv = mMainConn->CreateStatement(
1797 "SELECT scrolling_time FROM moz_places_metadata"_ns,
1798 getter_AddRefs(stmt));
1799 if (NS_FAILED(rv)) {
1800 rv = mMainConn->ExecuteSimpleSQL(
1801 "ALTER TABLE moz_places_metadata "
1802 "ADD COLUMN scrolling_time INTEGER NOT NULL DEFAULT 0 "_ns);
1803 NS_ENSURE_SUCCESS(rv, rv);
1806 rv = mMainConn->CreateStatement(
1807 "SELECT scrolling_distance FROM moz_places_metadata"_ns,
1808 getter_AddRefs(stmt));
1809 if (NS_FAILED(rv)) {
1810 rv = mMainConn->ExecuteSimpleSQL(
1811 "ALTER TABLE moz_places_metadata "
1812 "ADD COLUMN scrolling_distance INTEGER NOT NULL DEFAULT 0 "_ns);
1813 NS_ENSURE_SUCCESS(rv, rv);
1815 return NS_OK;
1818 nsresult Database::MigrateV60Up() {
1819 // Add the site_name column to moz_places.
1820 nsCOMPtr<mozIStorageStatement> stmt;
1821 nsresult rv = mMainConn->CreateStatement(
1822 "SELECT site_name FROM moz_places"_ns, getter_AddRefs(stmt));
1823 if (NS_FAILED(rv)) {
1824 rv = mMainConn->ExecuteSimpleSQL(
1825 "ALTER TABLE moz_places ADD COLUMN site_name TEXT"_ns);
1826 NS_ENSURE_SUCCESS(rv, rv);
1828 return NS_OK;
1831 nsresult Database::MigrateV61Up() {
1832 // Add previews tombstones table if necessary.
1833 nsCOMPtr<mozIStorageStatement> stmt;
1834 nsresult rv = mMainConn->CreateStatement(
1835 "SELECT hash FROM moz_previews_tombstones"_ns, getter_AddRefs(stmt));
1836 if (NS_FAILED(rv)) {
1837 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PREVIEWS_TOMBSTONES);
1838 NS_ENSURE_SUCCESS(rv, rv);
1840 return NS_OK;
1843 nsresult Database::MigrateV67Up() {
1844 // Align all input field in moz_inputhistory to lowercase. If there are
1845 // multiple records that expresses the same input, use maximum use_count from
1846 // them to carry on the experience of the past.
1847 nsCOMPtr<mozIStorageStatement> stmt;
1848 nsresult rv = mMainConn->ExecuteSimpleSQL(
1849 "INSERT INTO moz_inputhistory "
1850 "SELECT place_id, LOWER(input), use_count FROM moz_inputhistory "
1851 " WHERE LOWER(input) <> input "
1852 "ON CONFLICT DO "
1853 " UPDATE SET use_count = MAX(use_count, EXCLUDED.use_count)"_ns);
1854 NS_ENSURE_SUCCESS(rv, rv);
1855 rv = mMainConn->ExecuteSimpleSQL(
1856 "DELETE FROM moz_inputhistory WHERE LOWER(input) <> input"_ns);
1857 NS_ENSURE_SUCCESS(rv, rv);
1859 return NS_OK;
1862 nsresult Database::MigrateV69Up() {
1863 // Add source and annotation column to places table.
1864 nsCOMPtr<mozIStorageStatement> stmt;
1865 nsresult rv = mMainConn->CreateStatement(
1866 "SELECT source FROM moz_historyvisits"_ns, getter_AddRefs(stmt));
1867 if (NS_FAILED(rv)) {
1868 rv = mMainConn->ExecuteSimpleSQL(
1869 "ALTER TABLE moz_historyvisits "
1870 "ADD COLUMN source INTEGER DEFAULT 0 NOT NULL"_ns);
1871 NS_ENSURE_SUCCESS(rv, rv);
1872 rv = mMainConn->ExecuteSimpleSQL(
1873 "ALTER TABLE moz_historyvisits "
1874 "ADD COLUMN triggeringPlaceId INTEGER"_ns);
1875 NS_ENSURE_SUCCESS(rv, rv);
1878 return NS_OK;
1881 nsresult Database::MigrateV70Up() {
1882 nsCOMPtr<mozIStorageStatement> stmt;
1883 nsresult rv = mMainConn->CreateStatement(
1884 "SELECT recalc_frecency FROM moz_places LIMIT 1 "_ns,
1885 getter_AddRefs(stmt));
1886 if (NS_FAILED(rv)) {
1887 // Add recalc_frecency column, indicating frecency has to be recalculated.
1888 rv = mMainConn->ExecuteSimpleSQL(
1889 "ALTER TABLE moz_places "
1890 "ADD COLUMN recalc_frecency INTEGER NOT NULL DEFAULT 0 "_ns);
1891 NS_ENSURE_SUCCESS(rv, rv);
1894 // We must do the following updates regardless, for downgrade/upgrade cases.
1896 // moz_origins frecency is, at the time of this migration, the sum of all the
1897 // positive frecencies of pages linked to that origin. Frecencies that were
1898 // set to negative to request recalculation are thus not accounted for, and
1899 // since we're about to flip them to positive we should add them to their
1900 // origin. Then we must also update origins stats.
1901 // We ignore frecency = -1 because it's just an indication to recalculate
1902 // frecency and not an actual frecency value that was flipped, thus it would
1903 // not make sense to count it for the origin.
1904 rv = mMainConn->ExecuteSimpleSQL(
1905 "UPDATE moz_origins "
1906 "SET frecency = frecency + abs_frecency "
1907 "FROM (SELECT origin_id, ABS(frecency) AS abs_frecency FROM moz_places "
1908 "WHERE frecency < -1) AS places "
1909 "WHERE moz_origins.id = places.origin_id"_ns);
1910 NS_ENSURE_SUCCESS(rv, rv);
1911 rv = mMainConn->ExecuteSimpleSQL(
1912 "INSERT OR REPLACE INTO moz_meta(key, value) VALUES "
1913 "('origin_frecency_count', "
1914 "(SELECT COUNT(*) FROM moz_origins WHERE frecency > 0) "
1915 "), "
1916 "('origin_frecency_sum', "
1917 "(SELECT TOTAL(frecency) FROM moz_origins WHERE frecency > 0) "
1918 "), "
1919 "('origin_frecency_sum_of_squares', "
1920 "(SELECT TOTAL(frecency * frecency) FROM moz_origins WHERE frecency > 0) "
1921 ") "_ns);
1922 NS_ENSURE_SUCCESS(rv, rv);
1924 // Now set recalc_frecency = 1 and positive frecency to any page having a
1925 // negative frecency.
1926 // Note we don't flip frecency = -1, since we skipped it above when updating
1927 // origins, and it remains an acceptable value yet, until the recalculation.
1928 rv = mMainConn->ExecuteSimpleSQL(
1929 "UPDATE moz_places "
1930 "SET recalc_frecency = 1, "
1931 " frecency = CASE WHEN frecency = -1 THEN -1 ELSE ABS(frecency) END "
1932 "WHERE frecency < 0 "_ns);
1933 NS_ENSURE_SUCCESS(rv, rv);
1935 return NS_OK;
1938 nsresult Database::MigrateV71Up() {
1939 // Fix the foreign counts. We ignore failures as the tables may not exist.
1940 mMainConn->ExecuteSimpleSQL(
1941 "UPDATE moz_places "
1942 "SET foreign_count = foreign_count - 1 "
1943 "WHERE id in (SELECT place_id FROM moz_places_metadata_snapshots)"_ns);
1944 mMainConn->ExecuteSimpleSQL(
1945 "UPDATE moz_places "
1946 "SET foreign_count = foreign_count - 1 "
1947 "WHERE id in (SELECT place_id FROM moz_session_to_places)"_ns);
1949 // Remove unused snapshots and session tables and indexes.
1950 nsresult rv = mMainConn->ExecuteSimpleSQL(
1951 "DROP INDEX IF EXISTS moz_places_metadata_snapshots_pinnedindex"_ns);
1952 NS_ENSURE_SUCCESS(rv, rv);
1953 rv = mMainConn->ExecuteSimpleSQL(
1954 "DROP INDEX IF EXISTS moz_places_metadata_snapshots_extra_typeindex"_ns);
1955 NS_ENSURE_SUCCESS(rv, rv);
1956 rv = mMainConn->ExecuteSimpleSQL(
1957 "DROP TABLE IF EXISTS moz_places_metadata_groups_to_snapshots"_ns);
1958 NS_ENSURE_SUCCESS(rv, rv);
1959 rv = mMainConn->ExecuteSimpleSQL(
1960 "DROP TABLE IF EXISTS moz_places_metadata_snapshots_groups"_ns);
1961 NS_ENSURE_SUCCESS(rv, rv);
1962 rv = mMainConn->ExecuteSimpleSQL(
1963 "DROP TABLE IF EXISTS moz_places_metadata_snapshots_extra"_ns);
1964 NS_ENSURE_SUCCESS(rv, rv);
1965 rv = mMainConn->ExecuteSimpleSQL(
1966 "DROP TABLE IF EXISTS moz_places_metadata_snapshots"_ns);
1967 NS_ENSURE_SUCCESS(rv, rv);
1968 rv = mMainConn->ExecuteSimpleSQL(
1969 "DROP TABLE IF EXISTS moz_session_to_places"_ns);
1970 NS_ENSURE_SUCCESS(rv, rv);
1971 rv = mMainConn->ExecuteSimpleSQL(
1972 "DROP TABLE IF EXISTS moz_session_metadata"_ns);
1973 NS_ENSURE_SUCCESS(rv, rv);
1975 return NS_OK;
1978 nsresult Database::MigrateV72Up() {
1979 // Recalculate frecency of unvisited bookmarks.
1980 nsresult rv = mMainConn->ExecuteSimpleSQL(
1981 "UPDATE moz_places "
1982 "SET recalc_frecency = 1 "
1983 "WHERE foreign_count > 0 AND visit_count = 0"_ns);
1984 NS_ENSURE_SUCCESS(rv, rv);
1985 return NS_OK;
1988 nsresult Database::MigrateV73Up() {
1989 // Add recalc_frecency, alt_frecency and recalc_alt_frecency to moz_origins.
1990 nsCOMPtr<mozIStorageStatement> stmt;
1991 nsresult rv = mMainConn->CreateStatement(
1992 "SELECT recalc_frecency FROM moz_origins"_ns, getter_AddRefs(stmt));
1993 if (NS_FAILED(rv)) {
1994 rv = mMainConn->ExecuteSimpleSQL(
1995 "ALTER TABLE moz_origins "
1996 "ADD COLUMN recalc_frecency INTEGER NOT NULL DEFAULT 0"_ns);
1997 NS_ENSURE_SUCCESS(rv, rv);
1998 rv = mMainConn->ExecuteSimpleSQL(
1999 "ALTER TABLE moz_origins "
2000 "ADD COLUMN alt_frecency INTEGER"_ns);
2001 NS_ENSURE_SUCCESS(rv, rv);
2002 rv = mMainConn->ExecuteSimpleSQL(
2003 "ALTER TABLE moz_origins "
2004 "ADD COLUMN recalc_alt_frecency INTEGER NOT NULL DEFAULT 0"_ns);
2005 NS_ENSURE_SUCCESS(rv, rv);
2007 return NS_OK;
2010 nsresult Database::MigrateV74Up() {
2011 // Add alt_frecency and recalc_alt_frecency to moz_places.
2012 nsCOMPtr<mozIStorageStatement> stmt;
2013 nsresult rv = mMainConn->CreateStatement(
2014 "SELECT alt_frecency FROM moz_places"_ns, getter_AddRefs(stmt));
2015 if (NS_FAILED(rv)) {
2016 rv = mMainConn->ExecuteSimpleSQL(
2017 "ALTER TABLE moz_places "
2018 "ADD COLUMN alt_frecency INTEGER"_ns);
2019 NS_ENSURE_SUCCESS(rv, rv);
2020 rv = mMainConn->ExecuteSimpleSQL(
2021 "ALTER TABLE moz_places "
2022 "ADD COLUMN recalc_alt_frecency INTEGER NOT NULL DEFAULT 0"_ns);
2023 NS_ENSURE_SUCCESS(rv, rv);
2024 rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_ALT_FRECENCY);
2025 NS_ENSURE_SUCCESS(rv, rv);
2027 return NS_OK;
2030 nsresult Database::MigrateV75Up() {
2031 // Add *_extra tables for moz_places and moz_historyvisits
2032 nsCOMPtr<mozIStorageStatement> stmt;
2033 nsresult rv = mMainConn->CreateStatement(
2034 "SELECT sync_json FROM moz_places_extra"_ns, getter_AddRefs(stmt));
2035 if (NS_FAILED(rv)) {
2036 nsresult rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES_EXTRA);
2037 NS_ENSURE_SUCCESS(rv, rv);
2038 rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS_EXTRA);
2039 NS_ENSURE_SUCCESS(rv, rv);
2041 return NS_OK;
2044 int64_t Database::CreateMobileRoot() {
2045 MOZ_ASSERT(NS_IsMainThread());
2047 // Create the mobile root, ignoring conflicts if one already exists (for
2048 // example, if the user downgraded to an earlier release channel).
2049 nsCOMPtr<mozIStorageStatement> createStmt;
2050 nsresult rv = mMainConn->CreateStatement(
2051 nsLiteralCString(
2052 "INSERT OR IGNORE INTO moz_bookmarks "
2053 "(type, title, dateAdded, lastModified, guid, position, parent) "
2054 "SELECT :item_type, :item_title, :timestamp, :timestamp, :guid, "
2055 "IFNULL((SELECT MAX(position) + 1 FROM moz_bookmarks p WHERE "
2056 "p.parent = b.id), 0), b.id "
2057 "FROM moz_bookmarks b WHERE b.parent = 0"),
2058 getter_AddRefs(createStmt));
2059 if (NS_FAILED(rv)) return -1;
2061 rv = createStmt->BindInt32ByName("item_type"_ns,
2062 nsINavBookmarksService::TYPE_FOLDER);
2063 if (NS_FAILED(rv)) return -1;
2064 rv = createStmt->BindUTF8StringByName("item_title"_ns,
2065 nsLiteralCString(MOBILE_ROOT_TITLE));
2066 if (NS_FAILED(rv)) return -1;
2067 rv = createStmt->BindInt64ByName("timestamp"_ns, RoundedPRNow());
2068 if (NS_FAILED(rv)) return -1;
2069 rv = createStmt->BindUTF8StringByName("guid"_ns,
2070 nsLiteralCString(MOBILE_ROOT_GUID));
2071 if (NS_FAILED(rv)) return -1;
2073 rv = createStmt->Execute();
2074 if (NS_FAILED(rv)) return -1;
2076 // Find the mobile root ID. We can't use the last inserted ID because the
2077 // root might already exist, and we ignore on conflict.
2078 nsCOMPtr<mozIStorageStatement> findIdStmt;
2079 rv = mMainConn->CreateStatement(
2080 "SELECT id FROM moz_bookmarks WHERE guid = :guid"_ns,
2081 getter_AddRefs(findIdStmt));
2082 if (NS_FAILED(rv)) return -1;
2084 rv = findIdStmt->BindUTF8StringByName("guid"_ns,
2085 nsLiteralCString(MOBILE_ROOT_GUID));
2086 if (NS_FAILED(rv)) return -1;
2088 bool hasResult = false;
2089 rv = findIdStmt->ExecuteStep(&hasResult);
2090 if (NS_FAILED(rv) || !hasResult) return -1;
2092 int64_t rootId;
2093 rv = findIdStmt->GetInt64(0, &rootId);
2094 if (NS_FAILED(rv)) return -1;
2096 return rootId;
2099 void Database::Shutdown() {
2100 // As the last step in the shutdown path, finalize the database handle.
2101 MOZ_ASSERT(NS_IsMainThread());
2102 MOZ_ASSERT(!mClosed);
2104 // Break cycles with the shutdown blockers.
2105 mClientsShutdown = nullptr;
2106 nsCOMPtr<mozIStorageCompletionCallback> connectionShutdown =
2107 std::move(mConnectionShutdown);
2109 if (!mMainConn) {
2110 // The connection has never been initialized. Just mark it as closed.
2111 mClosed = true;
2112 (void)connectionShutdown->Complete(NS_OK, nullptr);
2113 return;
2116 #ifdef DEBUG
2118 bool hasResult;
2119 nsCOMPtr<mozIStorageStatement> stmt;
2121 // Sanity check for missing guids.
2122 nsresult rv =
2123 mMainConn->CreateStatement(nsLiteralCString("SELECT 1 "
2124 "FROM moz_places "
2125 "WHERE guid IS NULL "),
2126 getter_AddRefs(stmt));
2127 MOZ_ASSERT(NS_SUCCEEDED(rv));
2128 rv = stmt->ExecuteStep(&hasResult);
2129 MOZ_ASSERT(NS_SUCCEEDED(rv));
2130 MOZ_ASSERT(!hasResult, "Found a page without a GUID!");
2131 rv = mMainConn->CreateStatement(nsLiteralCString("SELECT 1 "
2132 "FROM moz_bookmarks "
2133 "WHERE guid IS NULL "),
2134 getter_AddRefs(stmt));
2135 MOZ_ASSERT(NS_SUCCEEDED(rv));
2136 rv = stmt->ExecuteStep(&hasResult);
2137 MOZ_ASSERT(NS_SUCCEEDED(rv));
2138 MOZ_ASSERT(!hasResult, "Found a bookmark without a GUID!");
2140 // Sanity check for unrounded dateAdded and lastModified values (bug
2141 // 1107308).
2142 rv = mMainConn->CreateStatement(
2143 nsLiteralCString(
2144 "SELECT 1 "
2145 "FROM moz_bookmarks "
2146 "WHERE dateAdded % 1000 > 0 OR lastModified % 1000 > 0 LIMIT 1"),
2147 getter_AddRefs(stmt));
2148 MOZ_ASSERT(NS_SUCCEEDED(rv));
2149 rv = stmt->ExecuteStep(&hasResult);
2150 MOZ_ASSERT(NS_SUCCEEDED(rv));
2151 MOZ_ASSERT(!hasResult, "Found unrounded dates!");
2153 // Sanity check url_hash
2154 rv = mMainConn->CreateStatement(
2155 "SELECT 1 FROM moz_places WHERE url_hash = 0"_ns, getter_AddRefs(stmt));
2156 MOZ_ASSERT(NS_SUCCEEDED(rv));
2157 rv = stmt->ExecuteStep(&hasResult);
2158 MOZ_ASSERT(NS_SUCCEEDED(rv));
2159 MOZ_ASSERT(!hasResult, "Found a place without a hash!");
2161 // Sanity check unique urls
2162 rv = mMainConn->CreateStatement(
2163 nsLiteralCString(
2164 "SELECT 1 FROM moz_places GROUP BY url HAVING count(*) > 1 "),
2165 getter_AddRefs(stmt));
2166 MOZ_ASSERT(NS_SUCCEEDED(rv));
2167 rv = stmt->ExecuteStep(&hasResult);
2168 MOZ_ASSERT(NS_SUCCEEDED(rv));
2169 MOZ_ASSERT(!hasResult, "Found a duplicate url!");
2171 // Sanity check NULL urls
2172 rv = mMainConn->CreateStatement(
2173 "SELECT 1 FROM moz_places WHERE url ISNULL "_ns, getter_AddRefs(stmt));
2174 MOZ_ASSERT(NS_SUCCEEDED(rv));
2175 rv = stmt->ExecuteStep(&hasResult);
2176 MOZ_ASSERT(NS_SUCCEEDED(rv));
2177 MOZ_ASSERT(!hasResult, "Found a NULL url!");
2179 #endif
2181 mMainThreadStatements.FinalizeStatements();
2182 mMainThreadAsyncStatements.FinalizeStatements();
2184 RefPtr<FinalizeStatementCacheProxy<mozIStorageStatement>> event =
2185 new FinalizeStatementCacheProxy<mozIStorageStatement>(
2186 mAsyncThreadStatements, NS_ISUPPORTS_CAST(nsIObserver*, this));
2187 DispatchToAsyncThread(event);
2189 mClosed = true;
2191 // Execute PRAGMA optimized as last step, this will ensure proper database
2192 // performance across restarts.
2193 nsCOMPtr<mozIStoragePendingStatement> ps;
2194 MOZ_ALWAYS_SUCCEEDS(mMainConn->ExecuteSimpleSQLAsync(
2195 "PRAGMA optimize(0x02)"_ns, nullptr, getter_AddRefs(ps)));
2197 if (NS_FAILED(mMainConn->AsyncClose(connectionShutdown))) {
2198 mozilla::Unused << connectionShutdown->Complete(NS_ERROR_UNEXPECTED,
2199 nullptr);
2201 mMainConn = nullptr;
2204 ////////////////////////////////////////////////////////////////////////////////
2205 //// nsIObserver
2207 NS_IMETHODIMP
2208 Database::Observe(nsISupports* aSubject, const char* aTopic,
2209 const char16_t* aData) {
2210 MOZ_ASSERT(NS_IsMainThread());
2211 if (strcmp(aTopic, TOPIC_PROFILE_CHANGE_TEARDOWN) == 0) {
2212 // Tests simulating shutdown may cause multiple notifications.
2213 if (PlacesShutdownBlocker::sIsStarted) {
2214 return NS_OK;
2217 nsCOMPtr<nsIObserverService> os = services::GetObserverService();
2218 NS_ENSURE_STATE(os);
2220 // If shutdown happens in the same mainthread loop as init, observers could
2221 // handle the places-init-complete notification after xpcom-shutdown, when
2222 // the connection does not exist anymore. Removing those observers would
2223 // be less expensive but may cause their RemoveObserver calls to throw.
2224 // Thus notify the topic now, so they stop listening for it.
2225 nsCOMPtr<nsISimpleEnumerator> e;
2226 if (NS_SUCCEEDED(os->EnumerateObservers(TOPIC_PLACES_INIT_COMPLETE,
2227 getter_AddRefs(e))) &&
2228 e) {
2229 bool hasMore = false;
2230 while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) {
2231 nsCOMPtr<nsISupports> supports;
2232 if (NS_SUCCEEDED(e->GetNext(getter_AddRefs(supports)))) {
2233 nsCOMPtr<nsIObserver> observer = do_QueryInterface(supports);
2234 (void)observer->Observe(observer, TOPIC_PLACES_INIT_COMPLETE,
2235 nullptr);
2240 // Notify all Places users that we are about to shutdown.
2241 (void)os->NotifyObservers(nullptr, TOPIC_PLACES_SHUTDOWN, nullptr);
2242 } else if (strcmp(aTopic, TOPIC_SIMULATE_PLACES_SHUTDOWN) == 0) {
2243 // This notification is (and must be) only used by tests that are trying
2244 // to simulate Places shutdown out of the normal shutdown path.
2246 // Tests simulating shutdown may cause re-entrance.
2247 if (PlacesShutdownBlocker::sIsStarted) {
2248 return NS_OK;
2251 // We are simulating a shutdown, so invoke the shutdown blockers,
2252 // wait for them, then proceed with connection shutdown.
2253 // Since we are already going through shutdown, but it's not the real one,
2254 // we won't need to block the real one anymore, so we can unblock it.
2256 nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase =
2257 GetProfileChangeTeardownPhase();
2258 if (shutdownPhase) {
2259 shutdownPhase->RemoveBlocker(mClientsShutdown.get());
2261 (void)mClientsShutdown->BlockShutdown(nullptr);
2264 // Spin the events loop until the clients are done.
2265 // Note, this is just for tests, specifically test_clearHistory_shutdown.js
2266 SpinEventLoopUntil("places:Database::Observe(SIMULATE_PLACES_SHUTDOWN)"_ns,
2267 [&]() {
2268 return mClientsShutdown->State() ==
2269 PlacesShutdownBlocker::States::RECEIVED_DONE;
2273 nsCOMPtr<nsIAsyncShutdownClient> shutdownPhase =
2274 GetProfileBeforeChangePhase();
2275 if (shutdownPhase) {
2276 shutdownPhase->RemoveBlocker(mConnectionShutdown.get());
2278 (void)mConnectionShutdown->BlockShutdown(nullptr);
2281 return NS_OK;
2284 } // namespace mozilla::places