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"
14 #include "nsIInterfaceRequestorUtils.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"
26 #include "nsFaviconService.h"
28 #include "nsAppDirectoryServiceDefs.h"
29 #include "nsDirectoryServiceUtils.h"
32 #include "nsPrintfCString.h"
33 #include "mozilla/Preferences.h"
34 #include "mozilla/Services.h"
35 #include "mozilla/Unused.h"
36 #include "mozIStorageService.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
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
{
111 ////////////////////////////////////////////////////////////////////////////////
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
) {
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.
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
) {
176 MOZ_FALLTHROUGH_ASSERT("Trying to set an unknown journal mode.");
177 // Fall through to the default DELETE journal.
179 journalMode
.AssignLiteral("delete");
181 case JOURNAL_TRUNCATE
:
182 journalMode
.AssignLiteral("truncate");
185 journalMode
.AssignLiteral("memory");
188 journalMode
.AssignLiteral("wal");
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")) {
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
,
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(
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), "
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
;
267 nsresult
SetupDurability(nsCOMPtr
<mozIStorageConnection
>& aDBConn
,
268 int32_t aDBPageSize
) {
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
);
278 // Be sure to set journal mode after page_size. WAL would prevent the
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
);
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
,
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
);
341 ////////////////////////////////////////////////////////////////////////////////
344 PLACES_FACTORY_SINGLETON_IMPLEMENTATION(Database
, gDatabase
)
346 NS_IMPL_ISUPPORTS(Database
, nsIObserver
, nsISupportsWeakReference
)
349 : mMainThreadStatements(mMainConn
),
350 mMainThreadAsyncStatements(mMainConn
),
351 mAsyncThreadStatements(mMainConn
),
353 mDatabaseStatus(nsINavHistoryService::DATABASE_STATUS_OK
),
355 mClientsShutdown(new ClientsShutdownBlocker()),
356 mConnectionShutdown(new ConnectionShutdownBlocker(this)),
358 mCacheObservers(TOPIC_PLACES_INIT_COMPLETE
),
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
);
372 already_AddRefed
<nsIAsyncShutdownClient
>
373 Database::GetProfileChangeTeardownPhase() {
374 nsCOMPtr
<nsIAsyncShutdownService
> asyncShutdownSvc
=
375 services::GetAsyncShutdownService();
376 MOZ_ASSERT(asyncShutdownSvc
);
377 if (NS_WARN_IF(!asyncShutdownSvc
)) {
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
)) {
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())) {
414 MOZ_ASSERT(NS_IsMainThread());
415 return mMainThreadAsyncStatements
.GetCachedStatement(aQuery
);
418 already_AddRefed
<mozIStorageStatement
> Database::GetStatement(
419 const nsACString
& aQuery
) {
420 if (PlacesShutdownBlocker::sIsStarted
) {
423 if (NS_IsMainThread()) {
424 if (NS_FAILED(EnsureConnection())) {
427 return mMainThreadStatements
.GetCachedStatement(aQuery
);
429 // In the async case, the connection must have been started on the main-thread
431 MOZ_ASSERT(mMainConn
);
432 return mAsyncThreadStatements
.GetCachedStatement(aQuery
);
435 already_AddRefed
<nsIAsyncShutdownClient
> Database::GetClientsShutdown() {
436 if (mClientsShutdown
) return mClientsShutdown
->GetClient();
440 already_AddRefed
<nsIAsyncShutdownClient
> Database::GetConnectionShutdown() {
441 if (mConnectionShutdown
) return mConnectionShutdown
->GetClient();
446 already_AddRefed
<Database
> Database::GetDatabase() {
447 if (PlacesShutdownBlocker::sIsStarted
) {
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
);
465 nsresult rv
= shutdownPhase
->AddBlocker(
466 static_cast<nsIAsyncShutdownBlocker
*>(mClientsShutdown
.get()),
467 NS_LITERAL_STRING_FROM_CSTRING(__FILE__
), __LINE__
, u
""_ns
);
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
);
482 nsresult rv
= shutdownPhase
->AddBlocker(
483 static_cast<nsIAsyncShutdownBlocker
*>(mConnectionShutdown
.get()),
484 NS_LITERAL_STRING_FROM_CSTRING(__FILE__
), __LINE__
, u
""_ns
);
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();
496 (void)os
->AddObserver(this, TOPIC_PROFILE_CHANGE_TEARDOWN
, true);
501 nsresult
Database::EnsureConnection() {
502 // Run this only once.
504 mDatabaseStatus
== nsINavHistoryService::DATABASE_STATUS_LOCKED
) {
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
) {
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
,
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
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
);
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
);
604 rv
= NS_ERROR_FILE_IS_LOCKED
;
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
,
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
);
646 NS_ENSURE_SUCCESS(rv
, rv
);
648 initSucceeded
= true;
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
) {
660 entries
[idx
]->Observe(nullptr, TOPIC_PLACES_INIT_COMPLETE
, nullptr));
662 nsCOMPtr
<nsIObserverService
> obs
= mozilla::services::GetObserverService();
665 obs
->NotifyObservers(nullptr, TOPIC_PLACES_INIT_COMPLETE
, nullptr));
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
);
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
),
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
715 rv
= conn
->ExecuteSimpleSQL("PRAGMA temp_store = MEMORY"_ns
);
716 NS_ENSURE_SUCCESS(rv
, rv
);
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
),
748 NS_ENSURE_SUCCESS(rv
, rv
);
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
;
761 // Due to OS file lockings, attached databases can't be cloned properly,
762 // otherwise trying to reattach them later would fail.
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
) {
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 {
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.
832 rv
= mMainConn
->SpinningSynchronousClose();
833 NS_ENSURE_SUCCESS(rv
, rv
);
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
) {
844 // Create a new database file and try to clone tables from the corrupt one.
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
;
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
;
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
);
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
) {
902 nsCOMPtr
<mozIStorageConnection
> conn
;
903 auto guard
= MakeScopeExit([&]() {
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
);
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(
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
) {
943 rv
= stmt
->GetUTF8String(0, name
);
944 NS_ENSURE_SUCCESS(rv
, rv
);
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
);
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(
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
);
969 while (NS_SUCCEEDED(stmt
->ExecuteStep(&hasResult
)) && hasResult
) {
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());
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
);
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
1029 rv
= mMainConn
->ExecuteSimpleSQL(nsLiteralCString(
1030 MOZ_STORAGE_UNIQUIFY_QUERY_STR
"PRAGMA temp_store = MEMORY"));
1031 NS_ENSURE_SUCCESS(rv
, rv
);
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
);
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");
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
) {
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
);
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(¤tSchemaVersion
);
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.
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
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.
1271 // This is a new database, so we have to create all the tables and indices.
1274 rv
= mMainConn
->ExecuteSimpleSQL(CREATE_MOZ_ORIGINS
);
1275 NS_ENSURE_SUCCESS(rv
, rv
);
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
);
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
);
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
);
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
);
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
);
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
);
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.
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 "
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
);
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;
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
);
1451 nsresult
Database::EnsureBookmarkRoots(const int32_t startPosition
,
1452 bool shouldReparentRoots
) {
1453 MOZ_ASSERT(NS_IsMainThread());
1458 // The first root's title is an empty string.
1459 rv
= CreateRoot(mMainConn
, "places"_ns
, "root________"_ns
, ""_ns
, 0,
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
;
1476 if (mToolbarRootId
< 1) {
1477 rv
= CreateRoot(mMainConn
, "toolbar"_ns
, "toolbar_____"_ns
, "toolbar"_ns
,
1478 position
, mToolbarRootId
);
1479 if (NS_FAILED(rv
)) return rv
;
1483 if (mTagsRootId
< 1) {
1484 rv
= CreateRoot(mMainConn
, "tags"_ns
, "tags________"_ns
, "tags"_ns
,
1485 position
, mTagsRootId
);
1486 if (NS_FAILED(rv
)) return rv
;
1490 if (mUnfiledRootId
< 1) {
1491 rv
= CreateRoot(mMainConn
, "unfiled"_ns
, "unfiled_____"_ns
, "unfiled"_ns
,
1492 position
, mUnfiledRootId
);
1493 if (NS_FAILED(rv
)) return rv
;
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
) {
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 "
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 "
1547 "WHERE id = NEW.id; "
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(
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, "
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
;
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
);
1633 nsresult
Database::InitTempEntities() {
1634 MOZ_ASSERT(NS_IsMainThread());
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
);
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
);
1690 mMainConn
->ExecuteSimpleSQL(CREATE_BOOKMARKS_DELETED_AFTERINSERT_TRIGGER
);
1691 NS_ENSURE_SUCCESS(rv
, 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
);
1703 mMainConn
->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS_AFTERUPDATE_TRIGGER
);
1704 NS_ENSURE_SUCCESS(rv
, rv
);
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.
1718 // Remove all item annotations but SYNC_PARENT_ANNO.
1719 rv
= mMainConn
->CreateStatement(
1721 "DELETE FROM moz_items_annos "
1722 "WHERE anno_attribute_id NOT IN ( "
1723 " SELECT id FROM moz_anno_attributes WHERE name = :anno_name "
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 "
1737 " SELECT DISTINCT anno_attribute_id FROM moz_annos "
1739 " SELECT DISTINCT anno_attribute_id FROM moz_items_annos "
1741 NS_ENSURE_SUCCESS(rv
, rv
);
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
);
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
);
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
);
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
);
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
);
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 "
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
);
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
);
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) "
1916 "('origin_frecency_sum', "
1917 "(SELECT TOTAL(frecency) FROM moz_origins WHERE frecency > 0) "
1919 "('origin_frecency_sum_of_squares', "
1920 "(SELECT TOTAL(frecency * frecency) FROM moz_origins WHERE frecency > 0) "
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
);
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
);
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
);
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
);
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
);
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
);
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(
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;
2093 rv
= findIdStmt
->GetInt64(0, &rootId
);
2094 if (NS_FAILED(rv
)) return -1;
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
);
2110 // The connection has never been initialized. Just mark it as closed.
2112 (void)connectionShutdown
->Complete(NS_OK
, nullptr);
2119 nsCOMPtr
<mozIStorageStatement
> stmt
;
2121 // Sanity check for missing guids.
2123 mMainConn
->CreateStatement(nsLiteralCString("SELECT 1 "
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
2142 rv
= mMainConn
->CreateStatement(
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(
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!");
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
);
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
,
2201 mMainConn
= nullptr;
2204 ////////////////////////////////////////////////////////////////////////////////
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
) {
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
))) &&
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
,
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
) {
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
,
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);
2284 } // namespace mozilla::places