michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "Database.h" michael@0: michael@0: #include "nsINavBookmarksService.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #include "nsIFile.h" michael@0: michael@0: #include "nsNavHistory.h" michael@0: #include "nsPlacesTables.h" michael@0: #include "nsPlacesIndexes.h" michael@0: #include "nsPlacesTriggers.h" michael@0: #include "nsPlacesMacros.h" michael@0: #include "SQLFunctions.h" michael@0: #include "Helpers.h" michael@0: michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "prsystem.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/Services.h" michael@0: #include "prtime.h" michael@0: michael@0: // Time between corrupt database backups. michael@0: #define RECENT_BACKUP_TIME_MICROSEC (int64_t)86400 * PR_USEC_PER_SEC // 24H michael@0: michael@0: // Filename of the database. michael@0: #define DATABASE_FILENAME NS_LITERAL_STRING("places.sqlite") michael@0: // Filename used to backup corrupt databases. michael@0: #define DATABASE_CORRUPT_FILENAME NS_LITERAL_STRING("places.sqlite.corrupt") michael@0: michael@0: // Set when the database file was found corrupt by a previous maintenance. michael@0: #define PREF_FORCE_DATABASE_REPLACEMENT "places.database.replaceOnStartup" michael@0: michael@0: // Set to specify the size of the places database growth increments in kibibytes michael@0: #define PREF_GROWTH_INCREMENT_KIB "places.database.growthIncrementKiB" michael@0: michael@0: // Maximum size for the WAL file. It should be small enough since in case of michael@0: // crashes we could lose all the transactions in the file. But a too small michael@0: // file could hurt performance. michael@0: #define DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES 512 michael@0: michael@0: #define BYTES_PER_KIBIBYTE 1024 michael@0: michael@0: // Old Sync GUID annotation. michael@0: #define SYNCGUID_ANNO NS_LITERAL_CSTRING("sync/guid") michael@0: michael@0: // Places string bundle, contains internationalized bookmark root names. michael@0: #define PLACES_BUNDLE "chrome://places/locale/places.properties" michael@0: michael@0: // Livemarks annotations. michael@0: #define LMANNO_FEEDURI "livemark/feedURI" michael@0: #define LMANNO_SITEURI "livemark/siteURI" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: namespace mozilla { michael@0: namespace places { michael@0: michael@0: namespace { michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Helpers michael@0: michael@0: /** michael@0: * Checks whether exists a database backup created not longer than michael@0: * RECENT_BACKUP_TIME_MICROSEC ago. michael@0: */ michael@0: bool michael@0: hasRecentCorruptDB() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsCOMPtr profDir; michael@0: NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profDir)); michael@0: NS_ENSURE_TRUE(profDir, false); michael@0: nsCOMPtr entries; michael@0: profDir->GetDirectoryEntries(getter_AddRefs(entries)); michael@0: NS_ENSURE_TRUE(entries, false); michael@0: bool hasMore; michael@0: while (NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) { michael@0: nsCOMPtr next; michael@0: entries->GetNext(getter_AddRefs(next)); michael@0: NS_ENSURE_TRUE(next, false); michael@0: nsCOMPtr currFile = do_QueryInterface(next); michael@0: NS_ENSURE_TRUE(currFile, false); michael@0: michael@0: nsAutoString leafName; michael@0: if (NS_SUCCEEDED(currFile->GetLeafName(leafName)) && michael@0: leafName.Length() >= DATABASE_CORRUPT_FILENAME.Length() && michael@0: leafName.Find(".corrupt", DATABASE_FILENAME.Length()) != -1) { michael@0: PRTime lastMod = 0; michael@0: currFile->GetLastModifiedTime(&lastMod); michael@0: NS_ENSURE_TRUE(lastMod > 0, false); michael@0: return (PR_Now() - lastMod) > RECENT_BACKUP_TIME_MICROSEC; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: /** michael@0: * Updates sqlite_stat1 table through ANALYZE. michael@0: * Since also nsPlacesExpiration.js executes ANALYZE, the analyzed tables michael@0: * must be the same in both components. So ensure they are in sync. michael@0: * michael@0: * @param aDBConn michael@0: * The database connection. michael@0: */ michael@0: nsresult michael@0: updateSQLiteStatistics(mozIStorageConnection* aDBConn) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: nsCOMPtr analyzePlacesStmt; michael@0: aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING( michael@0: "ANALYZE moz_places" michael@0: ), getter_AddRefs(analyzePlacesStmt)); michael@0: NS_ENSURE_STATE(analyzePlacesStmt); michael@0: nsCOMPtr analyzeBookmarksStmt; michael@0: aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING( michael@0: "ANALYZE moz_bookmarks" michael@0: ), getter_AddRefs(analyzeBookmarksStmt)); michael@0: NS_ENSURE_STATE(analyzeBookmarksStmt); michael@0: nsCOMPtr analyzeVisitsStmt; michael@0: aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING( michael@0: "ANALYZE moz_historyvisits" michael@0: ), getter_AddRefs(analyzeVisitsStmt)); michael@0: NS_ENSURE_STATE(analyzeVisitsStmt); michael@0: nsCOMPtr analyzeInputStmt; michael@0: aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING( michael@0: "ANALYZE moz_inputhistory" michael@0: ), getter_AddRefs(analyzeInputStmt)); michael@0: NS_ENSURE_STATE(analyzeInputStmt); michael@0: michael@0: mozIStorageBaseStatement *stmts[] = { michael@0: analyzePlacesStmt, michael@0: analyzeBookmarksStmt, michael@0: analyzeVisitsStmt, michael@0: analyzeInputStmt michael@0: }; michael@0: michael@0: nsCOMPtr ps; michael@0: (void)aDBConn->ExecuteAsync(stmts, ArrayLength(stmts), nullptr, michael@0: getter_AddRefs(ps)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /** michael@0: * Sets the connection journal mode to one of the JOURNAL_* types. michael@0: * michael@0: * @param aDBConn michael@0: * The database connection. michael@0: * @param aJournalMode michael@0: * One of the JOURNAL_* types. michael@0: * @returns the current journal mode. michael@0: * @note this may return a different journal mode than the required one, since michael@0: * setting it may fail. michael@0: */ michael@0: enum JournalMode michael@0: SetJournalMode(nsCOMPtr& aDBConn, michael@0: enum JournalMode aJournalMode) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: nsAutoCString journalMode; michael@0: switch (aJournalMode) { michael@0: default: michael@0: MOZ_ASSERT(false, "Trying to set an unknown journal mode."); michael@0: // Fall through to the default DELETE journal. michael@0: case JOURNAL_DELETE: michael@0: journalMode.AssignLiteral("delete"); michael@0: break; michael@0: case JOURNAL_TRUNCATE: michael@0: journalMode.AssignLiteral("truncate"); michael@0: break; michael@0: case JOURNAL_MEMORY: michael@0: journalMode.AssignLiteral("memory"); michael@0: break; michael@0: case JOURNAL_WAL: michael@0: journalMode.AssignLiteral("wal"); michael@0: break; michael@0: } michael@0: michael@0: nsCOMPtr statement; michael@0: nsAutoCString query(MOZ_STORAGE_UNIQUIFY_QUERY_STR michael@0: "PRAGMA journal_mode = "); michael@0: query.Append(journalMode); michael@0: aDBConn->CreateStatement(query, getter_AddRefs(statement)); michael@0: NS_ENSURE_TRUE(statement, JOURNAL_DELETE); michael@0: michael@0: bool hasResult = false; michael@0: if (NS_SUCCEEDED(statement->ExecuteStep(&hasResult)) && hasResult && michael@0: NS_SUCCEEDED(statement->GetUTF8String(0, journalMode))) { michael@0: if (journalMode.EqualsLiteral("delete")) { michael@0: return JOURNAL_DELETE; michael@0: } michael@0: if (journalMode.EqualsLiteral("truncate")) { michael@0: return JOURNAL_TRUNCATE; michael@0: } michael@0: if (journalMode.EqualsLiteral("memory")) { michael@0: return JOURNAL_MEMORY; michael@0: } michael@0: if (journalMode.EqualsLiteral("wal")) { michael@0: return JOURNAL_WAL; michael@0: } michael@0: // This is an unknown journal. michael@0: MOZ_ASSERT(true); michael@0: } michael@0: michael@0: return JOURNAL_DELETE; michael@0: } michael@0: michael@0: class ConnectionCloseCallback MOZ_FINAL : public mozIStorageCompletionCallback { michael@0: bool mDone; michael@0: michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_MOZISTORAGECOMPLETIONCALLBACK michael@0: ConnectionCloseCallback(); michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: ConnectionCloseCallback::Complete(nsresult, nsISupports*) michael@0: { michael@0: mDone = true; michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: MOZ_ASSERT(os); michael@0: if (!os) michael@0: return NS_OK; michael@0: DebugOnly rv = os->NotifyObservers(nullptr, michael@0: TOPIC_PLACES_CONNECTION_CLOSED, michael@0: nullptr); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: ConnectionCloseCallback::ConnectionCloseCallback() michael@0: : mDone(false) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS( michael@0: ConnectionCloseCallback michael@0: , mozIStorageCompletionCallback michael@0: ) michael@0: michael@0: nsresult michael@0: CreateRoot(nsCOMPtr& aDBConn, michael@0: const nsCString& aRootName, michael@0: const nsXPIDLString& titleString) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // The position of the new item in its folder. michael@0: static int32_t itemPosition = 0; michael@0: michael@0: // A single creation timestamp for all roots so that the root folder's michael@0: // last modification time isn't earlier than its childrens' creation time. michael@0: static PRTime timestamp = 0; michael@0: if (!timestamp) michael@0: timestamp = PR_Now(); michael@0: michael@0: // Create a new bookmark folder for the root. michael@0: nsCOMPtr stmt; michael@0: nsresult rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "INSERT INTO moz_bookmarks " michael@0: "(type, position, title, dateAdded, lastModified, guid, parent) " michael@0: "VALUES (:item_type, :item_position, :item_title," michael@0: ":date_added, :last_modified, GENERATE_GUID()," michael@0: "IFNULL((SELECT id FROM moz_bookmarks WHERE parent = 0), 0))" michael@0: ), getter_AddRefs(stmt)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_type"), michael@0: nsINavBookmarksService::TYPE_FOLDER); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("item_position"), itemPosition); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_title"), michael@0: NS_ConvertUTF16toUTF8(titleString)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("date_added"), timestamp); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), timestamp); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = stmt->Execute(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Create an entry in moz_bookmarks_roots to link the folder to the root. michael@0: nsCOMPtr newRootStmt; michael@0: rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "INSERT INTO moz_bookmarks_roots (root_name, folder_id) " michael@0: "VALUES (:root_name, " michael@0: "(SELECT id from moz_bookmarks WHERE " michael@0: " position = :item_position AND " michael@0: " parent = IFNULL((SELECT MIN(folder_id) FROM moz_bookmarks_roots), 0)))" michael@0: ), getter_AddRefs(newRootStmt)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = newRootStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("root_name"), michael@0: aRootName); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = newRootStmt->BindInt32ByName(NS_LITERAL_CSTRING("item_position"), michael@0: itemPosition); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = newRootStmt->Execute(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // The 'places' root is a folder containing the other roots. michael@0: // The first bookmark in a folder has position 0. michael@0: if (!aRootName.Equals("places")) michael@0: ++itemPosition; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: } // Anonymous namespace michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// Database michael@0: michael@0: PLACES_FACTORY_SINGLETON_IMPLEMENTATION(Database, gDatabase) michael@0: michael@0: NS_IMPL_ISUPPORTS(Database michael@0: , nsIObserver michael@0: , nsISupportsWeakReference michael@0: ) michael@0: michael@0: Database::Database() michael@0: : mMainThreadStatements(mMainConn) michael@0: , mMainThreadAsyncStatements(mMainConn) michael@0: , mAsyncThreadStatements(mMainConn) michael@0: , mDBPageSize(0) michael@0: , mDatabaseStatus(nsINavHistoryService::DATABASE_STATUS_OK) michael@0: , mShuttingDown(false) michael@0: , mClosed(false) michael@0: { michael@0: // Attempting to create two instances of the service? michael@0: MOZ_ASSERT(!gDatabase); michael@0: gDatabase = this; michael@0: } michael@0: michael@0: Database::~Database() michael@0: { michael@0: // Check to make sure it's us, in case somebody wrongly creates an extra michael@0: // instance of this singleton class. michael@0: MOZ_ASSERT(gDatabase == this); michael@0: michael@0: // Remove the static reference to the service. michael@0: if (gDatabase == this) { michael@0: gDatabase = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: Database::Init() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsCOMPtr storage = michael@0: do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID); michael@0: NS_ENSURE_STATE(storage); michael@0: michael@0: // Init the database file and connect to it. michael@0: bool databaseCreated = false; michael@0: nsresult rv = InitDatabaseFile(storage, &databaseCreated); michael@0: if (NS_SUCCEEDED(rv) && databaseCreated) { michael@0: mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CREATE; michael@0: } michael@0: else if (rv == NS_ERROR_FILE_CORRUPTED) { michael@0: // The database is corrupt, backup and replace it with a new one. michael@0: mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT; michael@0: rv = BackupAndReplaceDatabaseFile(storage); michael@0: // Fallback to catch-all handler, that notifies a database locked failure. michael@0: } michael@0: michael@0: // If the database connection still cannot be opened, it may just be locked michael@0: // by third parties. Send out a notification and interrupt initialization. michael@0: if (NS_FAILED(rv)) { michael@0: nsRefPtr lockedEvent = new PlacesEvent(TOPIC_DATABASE_LOCKED); michael@0: (void)NS_DispatchToMainThread(lockedEvent); michael@0: return rv; michael@0: } michael@0: michael@0: // Initialize the database schema. In case of failure the existing schema is michael@0: // is corrupt or incoherent, thus the database should be replaced. michael@0: bool databaseMigrated = false; michael@0: rv = InitSchema(&databaseMigrated); michael@0: if (NS_FAILED(rv)) { michael@0: mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_CORRUPT; michael@0: rv = BackupAndReplaceDatabaseFile(storage); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: // Try to initialize the schema again on the new database. michael@0: rv = InitSchema(&databaseMigrated); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (databaseMigrated) { michael@0: mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_UPGRADED; michael@0: } michael@0: michael@0: if (mDatabaseStatus != nsINavHistoryService::DATABASE_STATUS_OK) { michael@0: rv = updateSQLiteStatistics(MainConn()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Initialize here all the items that are not part of the on-disk database, michael@0: // like views, temp triggers or temp tables. The database should not be michael@0: // considered corrupt if any of the following fails. michael@0: michael@0: rv = InitTempTriggers(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Notify we have finished database initialization. michael@0: // Enqueue the notification, so if we init another service that requires michael@0: // nsNavHistoryService we don't recursive try to get it. michael@0: nsRefPtr completeEvent = michael@0: new PlacesEvent(TOPIC_PLACES_INIT_COMPLETE); michael@0: rv = NS_DispatchToMainThread(completeEvent); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Finally observe profile shutdown notifications. michael@0: nsCOMPtr os = mozilla::services::GetObserverService(); michael@0: if (os) { michael@0: (void)os->AddObserver(this, TOPIC_PROFILE_CHANGE_TEARDOWN, true); michael@0: (void)os->AddObserver(this, TOPIC_PROFILE_BEFORE_CHANGE, true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::InitDatabaseFile(nsCOMPtr& aStorage, michael@0: bool* aNewDatabaseCreated) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: *aNewDatabaseCreated = false; michael@0: michael@0: nsCOMPtr databaseFile; michael@0: nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, michael@0: getter_AddRefs(databaseFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = databaseFile->Append(DATABASE_FILENAME); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool databaseFileExists = false; michael@0: rv = databaseFile->Exists(&databaseFileExists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (databaseFileExists && michael@0: Preferences::GetBool(PREF_FORCE_DATABASE_REPLACEMENT, false)) { michael@0: // If this pref is set, Maintenance required a database replacement, due to michael@0: // integrity corruption. michael@0: // Be sure to clear the pref to avoid handling it more than once. michael@0: (void)Preferences::ClearUser(PREF_FORCE_DATABASE_REPLACEMENT); michael@0: michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: michael@0: // Open the database file. If it does not exist a new one will be created. michael@0: // Use an unshared connection, it will consume more memory but avoid shared michael@0: // cache contentions across threads. michael@0: rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aNewDatabaseCreated = !databaseFileExists; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::BackupAndReplaceDatabaseFile(nsCOMPtr& aStorage) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: nsCOMPtr profDir; michael@0: nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, michael@0: getter_AddRefs(profDir)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr databaseFile; michael@0: rv = profDir->Clone(getter_AddRefs(databaseFile)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = databaseFile->Append(DATABASE_FILENAME); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If we have michael@0: // already failed in the last 24 hours avoid to create another corrupt file, michael@0: // since doing so, in some situation, could cause us to create a new corrupt michael@0: // file at every try to access any Places service. That is bad because it michael@0: // would quickly fill the user's disk space without any notice. michael@0: if (!hasRecentCorruptDB()) { michael@0: nsCOMPtr backup; michael@0: (void)aStorage->BackupDatabaseFile(databaseFile, DATABASE_CORRUPT_FILENAME, michael@0: profDir, getter_AddRefs(backup)); michael@0: } michael@0: michael@0: // Close database connection if open. michael@0: if (mMainConn) { michael@0: // If there's any not finalized statement or this fails for any reason michael@0: // we won't be able to remove the database. michael@0: rv = mMainConn->Close(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Remove the broken database. michael@0: rv = databaseFile->Remove(false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Create a new database file. michael@0: // Use an unshared connection, it will consume more memory but avoid shared michael@0: // cache contentions across threads. michael@0: rv = aStorage->OpenUnsharedDatabase(databaseFile, getter_AddRefs(mMainConn)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::InitSchema(bool* aDatabaseMigrated) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: *aDatabaseMigrated = false; michael@0: michael@0: // WARNING: any statement executed before setting the journal mode must be michael@0: // finalized, since SQLite doesn't allow changing the journal mode if there michael@0: // is any outstanding statement. michael@0: michael@0: { michael@0: // Get the page size. This may be different than the default if the michael@0: // database file already existed with a different page size. michael@0: nsCOMPtr statement; michael@0: nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size" michael@0: ), getter_AddRefs(statement)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: bool hasResult = false; michael@0: rv = statement->ExecuteStep(&hasResult); michael@0: NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE); michael@0: rv = statement->GetInt32(0, &mDBPageSize); michael@0: NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && mDBPageSize > 0, NS_ERROR_UNEXPECTED); michael@0: } michael@0: michael@0: // Ensure that temp tables are held in memory, not on disk. michael@0: nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA temp_store = MEMORY")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Be sure to set journal mode after page_size. WAL would prevent the change michael@0: // otherwise. michael@0: if (JOURNAL_WAL == SetJournalMode(mMainConn, JOURNAL_WAL)) { michael@0: // Set the WAL journal size limit. We want it to be small, since in michael@0: // synchronous = NORMAL mode a crash could cause loss of all the michael@0: // transactions in the journal. For added safety we will also force michael@0: // checkpointing at strategic moments. michael@0: int32_t checkpointPages = michael@0: static_cast(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 1024 / mDBPageSize); michael@0: nsAutoCString checkpointPragma("PRAGMA wal_autocheckpoint = "); michael@0: checkpointPragma.AppendInt(checkpointPages); michael@0: rv = mMainConn->ExecuteSimpleSQL(checkpointPragma); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: // Ignore errors, if we fail here the database could be considered corrupt michael@0: // and we won't be able to go on, even if it's just matter of a bogus file michael@0: // system. The default mode (DELETE) will be fine in such a case. michael@0: (void)SetJournalMode(mMainConn, JOURNAL_TRUNCATE); michael@0: michael@0: // Set synchronous to FULL to ensure maximum data integrity, even in michael@0: // case of crashes or unclean shutdowns. michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "PRAGMA synchronous = FULL")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // The journal is usually free to grow for performance reasons, but it never michael@0: // shrinks back. Since the space taken may be problematic, especially on michael@0: // mobile devices, limit its size. michael@0: // Since exceeding the limit will cause a truncate, allow a slightly michael@0: // larger limit than DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES to reduce the number michael@0: // of times it is needed. michael@0: nsAutoCString journalSizePragma("PRAGMA journal_size_limit = "); michael@0: journalSizePragma.AppendInt(DATABASE_MAX_WAL_SIZE_IN_KIBIBYTES * 3); michael@0: (void)mMainConn->ExecuteSimpleSQL(journalSizePragma); michael@0: michael@0: // Grow places in |growthIncrementKiB| increments to limit fragmentation on disk. michael@0: // By default, it's 10 MB. michael@0: int32_t growthIncrementKiB = michael@0: Preferences::GetInt(PREF_GROWTH_INCREMENT_KIB, 10 * BYTES_PER_KIBIBYTE); michael@0: if (growthIncrementKiB > 0) { michael@0: (void)mMainConn->SetGrowthIncrement(growthIncrementKiB * BYTES_PER_KIBIBYTE, EmptyCString()); michael@0: } michael@0: michael@0: // We use our functions during migration, so initialize them now. michael@0: rv = InitFunctions(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Get the database schema version. michael@0: int32_t currentSchemaVersion; michael@0: rv = mMainConn->GetSchemaVersion(¤tSchemaVersion); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: bool databaseInitialized = currentSchemaVersion > 0; michael@0: michael@0: if (databaseInitialized && currentSchemaVersion == DATABASE_SCHEMA_VERSION) { michael@0: // The database is up to date and ready to go. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // We are going to update the database, so everything from now on should be in michael@0: // a transaction for performances. michael@0: mozStorageTransaction transaction(mMainConn, false); michael@0: michael@0: if (databaseInitialized) { michael@0: // Migration How-to: michael@0: // michael@0: // 1. increment PLACES_SCHEMA_VERSION. michael@0: // 2. implement a method that performs upgrade to your version from the michael@0: // previous one. michael@0: // michael@0: // NOTE: The downgrade process is pretty much complicated by the fact old michael@0: // versions cannot know what a new version is going to implement. michael@0: // The only thing we will do for downgrades is setting back the schema michael@0: // version, so that next upgrades will run again the migration step. michael@0: michael@0: if (currentSchemaVersion < DATABASE_SCHEMA_VERSION) { michael@0: *aDatabaseMigrated = true; michael@0: michael@0: if (currentSchemaVersion < 6) { michael@0: // These are early Firefox 3.0 alpha versions that are not supported michael@0: // anymore. In this case it's safer to just replace the database. michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: michael@0: // Firefox 3.0 uses schema version 6. michael@0: michael@0: if (currentSchemaVersion < 7) { michael@0: rv = MigrateV7Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (currentSchemaVersion < 8) { michael@0: rv = MigrateV8Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Firefox 3.5 uses schema version 8. michael@0: michael@0: if (currentSchemaVersion < 9) { michael@0: rv = MigrateV9Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (currentSchemaVersion < 10) { michael@0: rv = MigrateV10Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Firefox 3.6 uses schema version 10. michael@0: michael@0: if (currentSchemaVersion < 11) { michael@0: rv = MigrateV11Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Firefox 4 uses schema version 11. michael@0: michael@0: // Firefox 8 uses schema version 12. michael@0: michael@0: if (currentSchemaVersion < 13) { michael@0: rv = MigrateV13Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (currentSchemaVersion < 14) { michael@0: rv = MigrateV14Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (currentSchemaVersion < 15) { michael@0: rv = MigrateV15Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (currentSchemaVersion < 16) { michael@0: rv = MigrateV16Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Firefox 11 uses schema version 16. michael@0: michael@0: if (currentSchemaVersion < 17) { michael@0: rv = MigrateV17Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Firefox 12 uses schema version 17. michael@0: michael@0: if (currentSchemaVersion < 18) { michael@0: rv = MigrateV18Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (currentSchemaVersion < 19) { michael@0: rv = MigrateV19Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Firefox 13 uses schema version 19. michael@0: michael@0: if (currentSchemaVersion < 20) { michael@0: rv = MigrateV20Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (currentSchemaVersion < 21) { michael@0: rv = MigrateV21Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Firefox 14 uses schema version 21. michael@0: michael@0: if (currentSchemaVersion < 22) { michael@0: rv = MigrateV22Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Firefox 22 uses schema version 22. michael@0: michael@0: if (currentSchemaVersion < 23) { michael@0: rv = MigrateV23Up(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Firefox 24 uses schema version 23. michael@0: michael@0: // Schema Upgrades must add migration code here. michael@0: michael@0: rv = UpdateBookmarkRootTitles(); michael@0: // We don't want a broken localization to cause us to think michael@0: // the database is corrupt and needs to be replaced. michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv)); michael@0: } michael@0: } michael@0: else { michael@0: // This is a new database, so we have to create all the tables and indices. michael@0: michael@0: // moz_places. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_PLACES); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_URL); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FAVICON); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_REVHOST); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_VISITCOUNT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FRECENCY); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_LASTVISITDATE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // moz_historyvisits. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HISTORYVISITS); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_FROMVISIT); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_VISITDATE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // moz_inputhistory. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // moz_hosts. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // moz_bookmarks. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACETYPE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PARENTPOSITION); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // moz_bookmarks_roots. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_BOOKMARKS_ROOTS); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // moz_keywords. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_KEYWORDS); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // moz_favicons. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_FAVICONS); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // moz_anno_attributes. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNO_ATTRIBUTES); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // moz_annos. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ANNOS); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // moz_items_annos. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_ITEMS_ANNOS); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Initialize the bookmark roots in the new DB. michael@0: rv = CreateBookmarkRoots(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Set the schema version to the current one. michael@0: rv = mMainConn->SetSchemaVersion(DATABASE_SCHEMA_VERSION); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = transaction.Commit(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: ForceWALCheckpoint(); michael@0: michael@0: // ANY FAILURE IN THIS METHOD WILL CAUSE US TO MARK THE DATABASE AS CORRUPT michael@0: // AND TRY TO REPLACE IT. michael@0: // DO NOT PUT HERE ANYTHING THAT IS NOT RELATED TO INITIALIZATION OR MODIFYING michael@0: // THE DISK DATABASE. michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::CreateBookmarkRoots() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsCOMPtr bundleService = michael@0: services::GetStringBundleService(); michael@0: NS_ENSURE_STATE(bundleService); michael@0: nsCOMPtr bundle; michael@0: nsresult rv = bundleService->CreateBundle(PLACES_BUNDLE, getter_AddRefs(bundle)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsXPIDLString rootTitle; michael@0: // The first root's title is an empty string. michael@0: rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("places"), rootTitle); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Fetch the internationalized folder name from the string bundle. michael@0: rv = bundle->GetStringFromName(MOZ_UTF16("BookmarksMenuFolderTitle"), michael@0: getter_Copies(rootTitle)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("menu"), rootTitle); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = bundle->GetStringFromName(MOZ_UTF16("BookmarksToolbarFolderTitle"), michael@0: getter_Copies(rootTitle)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("toolbar"), rootTitle); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = bundle->GetStringFromName(MOZ_UTF16("TagsFolderTitle"), michael@0: getter_Copies(rootTitle)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("tags"), rootTitle); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = bundle->GetStringFromName(MOZ_UTF16("UnsortedBookmarksFolderTitle"), michael@0: getter_Copies(rootTitle)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("unfiled"), rootTitle); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: #if DEBUG michael@0: nsCOMPtr stmt; michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT " michael@0: "(SELECT COUNT(*) FROM moz_bookmarks), " michael@0: "(SELECT COUNT(*) FROM moz_bookmarks_roots), " michael@0: "(SELECT SUM(position) FROM moz_bookmarks WHERE " michael@0: "id IN (SELECT folder_id FROM moz_bookmarks_roots))" michael@0: ), getter_AddRefs(stmt)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: bool hasResult; michael@0: rv = stmt->ExecuteStep(&hasResult); michael@0: if (NS_FAILED(rv)) return rv; michael@0: MOZ_ASSERT(hasResult); michael@0: int32_t bookmarkCount = 0; michael@0: rv = stmt->GetInt32(0, &bookmarkCount); michael@0: if (NS_FAILED(rv)) return rv; michael@0: int32_t rootCount = 0; michael@0: rv = stmt->GetInt32(1, &rootCount); michael@0: if (NS_FAILED(rv)) return rv; michael@0: int32_t positionSum = 0; michael@0: rv = stmt->GetInt32(2, &positionSum); michael@0: if (NS_FAILED(rv)) return rv; michael@0: MOZ_ASSERT(bookmarkCount == 5 && rootCount == 5 && positionSum == 6); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::InitFunctions() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsresult rv = GetUnreversedHostFunction::create(mMainConn); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = MatchAutoCompleteFunction::create(mMainConn); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = CalculateFrecencyFunction::create(mMainConn); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = GenerateGUIDFunction::create(mMainConn); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = FixupURLFunction::create(mMainConn); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = FrecencyNotificationFunction::create(mMainConn); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::InitTempTriggers() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsresult rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Add the triggers that update the moz_hosts table as necessary. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERINSERT_TRIGGER); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERDELETE_TRIGGER); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_TYPED_TRIGGER); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::UpdateBookmarkRootTitles() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsCOMPtr bundleService = michael@0: services::GetStringBundleService(); michael@0: NS_ENSURE_STATE(bundleService); michael@0: michael@0: nsCOMPtr bundle; michael@0: nsresult rv = bundleService->CreateBundle(PLACES_BUNDLE, getter_AddRefs(bundle)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr stmt; michael@0: rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_bookmarks SET title = :new_title WHERE id = " michael@0: "(SELECT folder_id FROM moz_bookmarks_roots WHERE root_name = :root_name)" michael@0: ), getter_AddRefs(stmt)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr paramsArray; michael@0: rv = stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: const char *rootNames[] = { "menu", "toolbar", "tags", "unfiled" }; michael@0: const char *titleStringIDs[] = { michael@0: "BookmarksMenuFolderTitle", "BookmarksToolbarFolderTitle", michael@0: "TagsFolderTitle", "UnsortedBookmarksFolderTitle" michael@0: }; michael@0: michael@0: for (uint32_t i = 0; i < ArrayLength(rootNames); ++i) { michael@0: nsXPIDLString title; michael@0: rv = bundle->GetStringFromName(NS_ConvertASCIItoUTF16(titleStringIDs[i]).get(), michael@0: getter_Copies(title)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr params; michael@0: rv = paramsArray->NewBindingParams(getter_AddRefs(params)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("root_name"), michael@0: nsDependentCString(rootNames[i])); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("new_title"), michael@0: NS_ConvertUTF16toUTF8(title)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = paramsArray->AddParams(params); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: rv = stmt->BindParameters(paramsArray); michael@0: if (NS_FAILED(rv)) return rv; michael@0: nsCOMPtr pendingStmt; michael@0: rv = stmt->ExecuteAsync(nullptr, getter_AddRefs(pendingStmt)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::CheckAndUpdateGUIDs() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // First, import any bookmark guids already set by Sync. michael@0: nsCOMPtr updateStmt; michael@0: nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_bookmarks " michael@0: "SET guid = :guid " michael@0: "WHERE id = :item_id " michael@0: ), getter_AddRefs(updateStmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr stmt; michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT item_id, content " michael@0: "FROM moz_items_annos " michael@0: "JOIN moz_anno_attributes " michael@0: "WHERE name = :anno_name " michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), michael@0: SYNCGUID_ANNO); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool hasResult; michael@0: while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { michael@0: int64_t itemId; michael@0: rv = stmt->GetInt64(0, &itemId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsAutoCString guid; michael@0: rv = stmt->GetUTF8String(1, guid); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If we have an invalid guid, we don't need to do any more work. michael@0: if (!IsValidGUID(guid)) { michael@0: continue; michael@0: } michael@0: michael@0: mozStorageStatementScoper updateScoper(updateStmt); michael@0: rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), itemId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = updateStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = updateStmt->Execute(); michael@0: if (rv == NS_ERROR_STORAGE_CONSTRAINT) { michael@0: // We just tried to insert a duplicate guid. Ignore this error, and we michael@0: // will generate a new one next. michael@0: continue; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Now, remove all the bookmark guid annotations that we just imported. michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "DELETE FROM moz_items_annos " michael@0: "WHERE anno_attribute_id = ( " michael@0: "SELECT id " michael@0: "FROM moz_anno_attributes " michael@0: "WHERE name = :anno_name " michael@0: ") " michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), michael@0: SYNCGUID_ANNO); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Next, generate guids for any bookmark that does not already have one. michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_bookmarks " michael@0: "SET guid = GENERATE_GUID() " michael@0: "WHERE guid IS NULL " michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Now, import any history guids already set by Sync. michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_places " michael@0: "SET guid = :guid " michael@0: "WHERE id = :place_id " michael@0: ), getter_AddRefs(updateStmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT place_id, content " michael@0: "FROM moz_annos " michael@0: "JOIN moz_anno_attributes " michael@0: "WHERE name = :anno_name " michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), michael@0: SYNCGUID_ANNO); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) { michael@0: int64_t placeId; michael@0: rv = stmt->GetInt64(0, &placeId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsAutoCString guid; michael@0: rv = stmt->GetUTF8String(1, guid); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If we have an invalid guid, we don't need to do any more work. michael@0: if (!IsValidGUID(guid)) { michael@0: continue; michael@0: } michael@0: michael@0: mozStorageStatementScoper updateScoper(updateStmt); michael@0: rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("place_id"), placeId); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = updateStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = updateStmt->Execute(); michael@0: if (rv == NS_ERROR_STORAGE_CONSTRAINT) { michael@0: // We just tried to insert a duplicate guid. Ignore this error, and we michael@0: // will generate a new one next. michael@0: continue; michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Now, remove all the place guid annotations that we just imported. michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "DELETE FROM moz_annos " michael@0: "WHERE anno_attribute_id = ( " michael@0: "SELECT id " michael@0: "FROM moz_anno_attributes " michael@0: "WHERE name = :anno_name " michael@0: ") " michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"), michael@0: SYNCGUID_ANNO); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Finally, generate guids for any places that do not already have one. michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_places " michael@0: "SET guid = GENERATE_GUID() " michael@0: "WHERE guid IS NULL " michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = stmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::MigrateV7Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Some old v6 databases come from alpha versions that missed indices. michael@0: // Just bail out and replace the database in such a case. michael@0: bool URLUniqueIndexExists = false; michael@0: nsresult rv = mMainConn->IndexExists(NS_LITERAL_CSTRING( michael@0: "moz_places_url_uniqueindex" michael@0: ), &URLUniqueIndexExists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!URLUniqueIndexExists) { michael@0: return NS_ERROR_FILE_CORRUPTED; michael@0: } michael@0: michael@0: mozStorageTransaction transaction(mMainConn, false); michael@0: michael@0: // We need an index on lastModified to catch quickly last modified bookmark michael@0: // title for tag container's children. This will be useful for Sync, too. michael@0: bool lastModIndexExists = false; michael@0: rv = mMainConn->IndexExists( michael@0: NS_LITERAL_CSTRING("moz_bookmarks_itemlastmodifiedindex"), michael@0: &lastModIndexExists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!lastModIndexExists) { michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // We need to do a one-time change of the moz_historyvisits.pageindex michael@0: // to speed up finding last visit date when joinin with moz_places. michael@0: // See bug 392399 for more details. michael@0: bool pageIndexExists = false; michael@0: rv = mMainConn->IndexExists( michael@0: NS_LITERAL_CSTRING("moz_historyvisits_pageindex"), &pageIndexExists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (pageIndexExists) { michael@0: // drop old index michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP INDEX IF EXISTS moz_historyvisits_pageindex")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // create the new multi-column index michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // for existing profiles, we may not have a frecency column michael@0: nsCOMPtr hasFrecencyStatement; michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT frecency FROM moz_places"), michael@0: getter_AddRefs(hasFrecencyStatement)); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // Add frecency column to moz_places, default to -1 so that all the michael@0: // frecencies are invalid michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "ALTER TABLE moz_places ADD frecency INTEGER DEFAULT -1 NOT NULL")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // create index for the frecency column michael@0: // XXX multi column index with typed, and visit_count? michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FRECENCY); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Invalidate all frecencies, since they need recalculation. michael@0: nsCOMPtr stmt = GetAsyncStatement( michael@0: "UPDATE moz_places SET frecency = ( " michael@0: "CASE " michael@0: "WHEN url BETWEEN 'place:' AND 'place;' " michael@0: "THEN 0 " michael@0: "ELSE -1 " michael@0: "END " michael@0: ") " michael@0: ); michael@0: NS_ENSURE_STATE(stmt); michael@0: nsCOMPtr ps; michael@0: (void)stmt->ExecuteAsync(nullptr, getter_AddRefs(ps)); michael@0: } michael@0: michael@0: // Temporary migration code for bug 396300 michael@0: nsCOMPtr moveUnfiledBookmarks; michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_bookmarks " michael@0: "SET parent = (" michael@0: "SELECT folder_id " michael@0: "FROM moz_bookmarks_roots " michael@0: "WHERE root_name = :root_name " michael@0: ") " michael@0: "WHERE type = :item_type " michael@0: "AND parent = (" michael@0: "SELECT folder_id " michael@0: "FROM moz_bookmarks_roots " michael@0: "WHERE root_name = :parent_name " michael@0: ")"), michael@0: getter_AddRefs(moveUnfiledBookmarks)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = moveUnfiledBookmarks->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("root_name"), NS_LITERAL_CSTRING("unfiled") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = moveUnfiledBookmarks->BindInt32ByName( michael@0: NS_LITERAL_CSTRING("item_type"), nsINavBookmarksService::TYPE_BOOKMARK michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = moveUnfiledBookmarks->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("parent_name"), NS_LITERAL_CSTRING("places") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = moveUnfiledBookmarks->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Create a statement to test for trigger creation michael@0: nsCOMPtr triggerDetection; michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT name " michael@0: "FROM sqlite_master " michael@0: "WHERE type = 'trigger' " michael@0: "AND name = :trigger_name"), michael@0: getter_AddRefs(triggerDetection)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Check for existence michael@0: bool triggerExists; michael@0: rv = triggerDetection->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("trigger_name"), michael@0: NS_LITERAL_CSTRING("moz_historyvisits_afterinsert_v1_trigger") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = triggerDetection->ExecuteStep(&triggerExists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = triggerDetection->Reset(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We need to create two triggers on moz_historyvists to maintain the michael@0: // accuracy of moz_places.visit_count. For this to work, we must ensure that michael@0: // all moz_places.visit_count values are correct. michael@0: // See bug 416313 for details. michael@0: if (!triggerExists) { michael@0: // First, we do a one-time reset of all the moz_places.visit_count values. michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_places SET visit_count = " michael@0: "(SELECT count(*) FROM moz_historyvisits " michael@0: "WHERE place_id = moz_places.id " michael@0: "AND visit_type NOT IN ") + michael@0: nsPrintfCString("(0,%d,%d,%d) ", michael@0: nsINavHistoryService::TRANSITION_EMBED, michael@0: nsINavHistoryService::TRANSITION_FRAMED_LINK, michael@0: nsINavHistoryService::TRANSITION_DOWNLOAD) + michael@0: NS_LITERAL_CSTRING(")")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We used to create two triggers here, but we no longer need that with michael@0: // schema version eight and greater. We've removed their creation here as michael@0: // a result. michael@0: } michael@0: michael@0: // Check for existence michael@0: rv = triggerDetection->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("trigger_name"), michael@0: NS_LITERAL_CSTRING("moz_bookmarks_beforedelete_v1_trigger") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = triggerDetection->ExecuteStep(&triggerExists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = triggerDetection->Reset(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // We need to create one trigger on moz_bookmarks to remove unused keywords. michael@0: // See bug 421180 for details. michael@0: if (!triggerExists) { michael@0: // First, remove any existing dangling keywords michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DELETE FROM moz_keywords " michael@0: "WHERE id IN (" michael@0: "SELECT k.id " michael@0: "FROM moz_keywords k " michael@0: "LEFT OUTER JOIN moz_bookmarks b " michael@0: "ON b.keyword_id = k.id " michael@0: "WHERE b.id IS NULL" michael@0: ")")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Add the moz_inputhistory table, if missing. michael@0: bool tableExists = false; michael@0: rv = mMainConn->TableExists(NS_LITERAL_CSTRING("moz_inputhistory"), michael@0: &tableExists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (!tableExists) { michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return transaction.Commit(); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: Database::MigrateV8Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mozStorageTransaction transaction(mMainConn, false); michael@0: michael@0: nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TRIGGER IF EXISTS moz_historyvisits_afterinsert_v1_trigger")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TRIGGER IF EXISTS moz_historyvisits_afterdelete_v1_trigger")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: michael@0: // bug #381795 - remove unused indexes michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP INDEX IF EXISTS moz_places_titleindex")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP INDEX IF EXISTS moz_annos_item_idindex")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP INDEX IF EXISTS moz_annos_place_idindex")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Do a one-time re-creation of the moz_annos indexes (bug 415201) michael@0: bool oldIndexExists = false; michael@0: rv = mMainConn->IndexExists(NS_LITERAL_CSTRING("moz_annos_attributesindex"), &oldIndexExists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: if (oldIndexExists) { michael@0: // drop old uri annos index michael@0: rv = mMainConn->ExecuteSimpleSQL( michael@0: NS_LITERAL_CSTRING("DROP INDEX moz_annos_attributesindex")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // create new uri annos index michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // drop old item annos index michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP INDEX IF EXISTS moz_items_annos_attributesindex")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // create new item annos index michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return transaction.Commit(); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: Database::MigrateV9Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: mozStorageTransaction transaction(mMainConn, false); michael@0: // Added in Bug 488966. The last_visit_date column caches the last michael@0: // visit date, this enhances SELECT performances when we michael@0: // need to sort visits by visit date. michael@0: // The cached value is synced by triggers on every added or removed visit. michael@0: // See nsPlacesTriggers.h for details on the triggers. michael@0: bool oldIndexExists = false; michael@0: nsresult rv = mMainConn->IndexExists( michael@0: NS_LITERAL_CSTRING("moz_places_lastvisitdateindex"), &oldIndexExists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!oldIndexExists) { michael@0: // Add last_visit_date column to moz_places. michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "ALTER TABLE moz_places ADD last_visit_date INTEGER")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_LASTVISITDATE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Now let's sync the column contents with real visit dates. michael@0: // This query can be really slow due to disk access, since it will basically michael@0: // dupe the table contents in the journal file, and then write them down michael@0: // in the database. michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_places SET last_visit_date = " michael@0: "(SELECT MAX(visit_date) " michael@0: "FROM moz_historyvisits " michael@0: "WHERE place_id = moz_places.id)")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return transaction.Commit(); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: Database::MigrateV10Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: // LastModified is set to the same value as dateAdded on item creation. michael@0: // This way we can use lastModified index to sort. michael@0: nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_bookmarks SET lastModified = dateAdded " michael@0: "WHERE lastModified IS NULL")); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: Database::MigrateV11Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: // Temp tables are going away. michael@0: // For triggers correctness, every time we pass through this migration michael@0: // step, we must ensure correctness of visit_count values. michael@0: nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_places SET visit_count = " michael@0: "(SELECT count(*) FROM moz_historyvisits " michael@0: "WHERE place_id = moz_places.id " michael@0: "AND visit_type NOT IN ") + michael@0: nsPrintfCString("(0,%d,%d,%d) ", michael@0: nsINavHistoryService::TRANSITION_EMBED, michael@0: nsINavHistoryService::TRANSITION_FRAMED_LINK, michael@0: nsINavHistoryService::TRANSITION_DOWNLOAD) + michael@0: NS_LITERAL_CSTRING(")") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // For existing profiles, we may not have a moz_bookmarks.guid column michael@0: nsCOMPtr hasGuidStatement; michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT guid FROM moz_bookmarks"), michael@0: getter_AddRefs(hasGuidStatement)); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // moz_bookmarks grew a guid column. Add the column, but do not populate it michael@0: // with anything just yet. We will do that soon. michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "ALTER TABLE moz_bookmarks " michael@0: "ADD COLUMN guid TEXT" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // moz_places grew a guid column. Add the column, but do not populate it michael@0: // with anything just yet. We will do that soon. michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "ALTER TABLE moz_places " michael@0: "ADD COLUMN guid TEXT" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // We need to update our guids before we do any real database work. michael@0: rv = CheckAndUpdateGUIDs(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::MigrateV13Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Dynamic containers are no longer supported. michael@0: nsCOMPtr deleteDynContainersStmt; michael@0: nsresult rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING( michael@0: "DELETE FROM moz_bookmarks WHERE type = :item_type"), michael@0: getter_AddRefs(deleteDynContainersStmt)); michael@0: rv = deleteDynContainersStmt->BindInt32ByName( michael@0: NS_LITERAL_CSTRING("item_type"), michael@0: nsINavBookmarksService::TYPE_DYNAMIC_CONTAINER michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsCOMPtr ps; michael@0: rv = deleteDynContainersStmt->ExecuteAsync(nullptr, getter_AddRefs(ps)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::MigrateV14Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // For existing profiles, we may not have a moz_favicons.guid column. michael@0: // Add it here. We want it to be unique, but ALTER TABLE doesn't allow michael@0: // a uniqueness constraint, so the index must be created separately. michael@0: nsCOMPtr hasGuidStatement; michael@0: nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT guid FROM moz_favicons"), michael@0: getter_AddRefs(hasGuidStatement)); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "ALTER TABLE moz_favicons " michael@0: "ADD COLUMN guid TEXT" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_FAVICONS_GUID); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Generate GUID for any favicon missing it. michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_favicons " michael@0: "SET guid = GENERATE_GUID() " michael@0: "WHERE guid ISNULL " michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::MigrateV15Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Drop moz_bookmarks_beforedelete_v1_trigger, since it's more expensive than michael@0: // useful. michael@0: nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TRIGGER IF EXISTS moz_bookmarks_beforedelete_v1_trigger" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Remove any orphan keywords. michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DELETE FROM moz_keywords " michael@0: "WHERE NOT EXISTS ( " michael@0: "SELECT id " michael@0: "FROM moz_bookmarks " michael@0: "WHERE keyword_id = moz_keywords.id " michael@0: ")" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::MigrateV16Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Due to Bug 715268 downgraded and then upgraded profiles may lack favicons michael@0: // guids, so fillup any missing ones. michael@0: nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_favicons " michael@0: "SET guid = GENERATE_GUID() " michael@0: "WHERE guid ISNULL " michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::MigrateV17Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: bool tableExists = false; michael@0: michael@0: nsresult rv = mMainConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!tableExists) { michael@0: // For anyone who used in-development versions of this autocomplete, michael@0: // drop the old tables and its indexes. michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP INDEX IF EXISTS moz_hostnames_frecencyindex" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP TABLE IF EXISTS moz_hostnames" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Add the moz_hosts table so we can get hostnames for URL autocomplete. michael@0: rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_HOSTS); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Fill the moz_hosts table with all the domains in moz_places. michael@0: nsCOMPtr fillHostsStmt; michael@0: rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING( michael@0: "INSERT OR IGNORE INTO moz_hosts (host, frecency) " michael@0: "SELECT fixup_url(get_unreversed_host(h.rev_host)) AS host, " michael@0: "(SELECT MAX(frecency) FROM moz_places " michael@0: "WHERE rev_host = h.rev_host " michael@0: "OR rev_host = h.rev_host || 'www.' " michael@0: ") AS frecency " michael@0: "FROM moz_places h " michael@0: "WHERE LENGTH(h.rev_host) > 1 " michael@0: "GROUP BY h.rev_host" michael@0: ), getter_AddRefs(fillHostsStmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr ps; michael@0: rv = fillHostsStmt->ExecuteAsync(nullptr, getter_AddRefs(ps)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::MigrateV18Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // moz_hosts should distinguish on typed entries. michael@0: michael@0: // Check if the profile already has a typed column. michael@0: nsCOMPtr stmt; michael@0: nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT typed FROM moz_hosts" michael@0: ), getter_AddRefs(stmt)); michael@0: if (NS_FAILED(rv)) { michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "ALTER TABLE moz_hosts ADD COLUMN typed NOT NULL DEFAULT 0" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // With the addition of the typed column the covering index loses its michael@0: // advantages. On the other side querying on host and (optionally) typed michael@0: // largely restricts the number of results, making scans decently fast. michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "DROP INDEX IF EXISTS moz_hosts_frecencyhostindex" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Update typed data. michael@0: nsCOMPtr updateTypedStmt; michael@0: rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_hosts SET typed = 1 WHERE host IN ( " michael@0: "SELECT fixup_url(get_unreversed_host(rev_host)) " michael@0: "FROM moz_places WHERE typed = 1 " michael@0: ") " michael@0: ), getter_AddRefs(updateTypedStmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr ps; michael@0: rv = updateTypedStmt->ExecuteAsync(nullptr, getter_AddRefs(ps)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::MigrateV19Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Livemarks children are no longer bookmarks. michael@0: michael@0: // Remove all children of folders annotated as livemarks. michael@0: nsCOMPtr deleteLivemarksChildrenStmt; michael@0: nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "DELETE FROM moz_bookmarks WHERE parent IN(" michael@0: "SELECT b.id FROM moz_bookmarks b " michael@0: "JOIN moz_items_annos a ON a.item_id = b.id " michael@0: "JOIN moz_anno_attributes n ON n.id = a.anno_attribute_id " michael@0: "WHERE b.type = :item_type AND n.name = :anno_name " michael@0: ")" michael@0: ), getter_AddRefs(deleteLivemarksChildrenStmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteLivemarksChildrenStmt->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("anno_name"), NS_LITERAL_CSTRING(LMANNO_FEEDURI) michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteLivemarksChildrenStmt->BindInt32ByName( michael@0: NS_LITERAL_CSTRING("item_type"), nsINavBookmarksService::TYPE_FOLDER michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteLivemarksChildrenStmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Clear obsolete livemark prefs. michael@0: (void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_seconds"); michael@0: (void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_limit_count"); michael@0: (void)Preferences::ClearUser("browser.bookmarks.livemark_refresh_delay_time"); michael@0: michael@0: // Remove the old status annotations. michael@0: nsCOMPtr deleteLivemarksAnnosStmt; michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "DELETE FROM moz_items_annos WHERE anno_attribute_id IN(" michael@0: "SELECT id FROM moz_anno_attributes " michael@0: "WHERE name IN (:anno_loading, :anno_loadfailed, :anno_expiration) " michael@0: ")" michael@0: ), getter_AddRefs(deleteLivemarksAnnosStmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteLivemarksAnnosStmt->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("anno_loading"), NS_LITERAL_CSTRING("livemark/loading") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteLivemarksAnnosStmt->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("anno_loadfailed"), NS_LITERAL_CSTRING("livemark/loadfailed") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteLivemarksAnnosStmt->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("anno_expiration"), NS_LITERAL_CSTRING("livemark/expiration") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteLivemarksAnnosStmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Remove orphan annotation names. michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "DELETE FROM moz_anno_attributes " michael@0: "WHERE name IN (:anno_loading, :anno_loadfailed, :anno_expiration) " michael@0: ), getter_AddRefs(deleteLivemarksAnnosStmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteLivemarksAnnosStmt->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("anno_loading"), NS_LITERAL_CSTRING("livemark/loading") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteLivemarksAnnosStmt->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("anno_loadfailed"), NS_LITERAL_CSTRING("livemark/loadfailed") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteLivemarksAnnosStmt->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("anno_expiration"), NS_LITERAL_CSTRING("livemark/expiration") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteLivemarksAnnosStmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::MigrateV20Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Remove obsolete bookmark GUID annotations. michael@0: nsCOMPtr deleteOldBookmarkGUIDAnnosStmt; michael@0: nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "DELETE FROM moz_items_annos WHERE anno_attribute_id = (" michael@0: "SELECT id FROM moz_anno_attributes " michael@0: "WHERE name = :anno_guid" michael@0: ")" michael@0: ), getter_AddRefs(deleteOldBookmarkGUIDAnnosStmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteOldBookmarkGUIDAnnosStmt->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("anno_guid"), NS_LITERAL_CSTRING("placesInternal/GUID") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteOldBookmarkGUIDAnnosStmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Remove the orphan annotation name. michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "DELETE FROM moz_anno_attributes " michael@0: "WHERE name = :anno_guid" michael@0: ), getter_AddRefs(deleteOldBookmarkGUIDAnnosStmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteOldBookmarkGUIDAnnosStmt->BindUTF8StringByName( michael@0: NS_LITERAL_CSTRING("anno_guid"), NS_LITERAL_CSTRING("placesInternal/GUID") michael@0: ); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = deleteOldBookmarkGUIDAnnosStmt->Execute(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::MigrateV21Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Add a prefix column to moz_hosts. michael@0: nsCOMPtr stmt; michael@0: nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT prefix FROM moz_hosts" michael@0: ), getter_AddRefs(stmt)); michael@0: if (NS_FAILED(rv)) { michael@0: rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "ALTER TABLE moz_hosts ADD COLUMN prefix" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: Database::MigrateV22Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Reset all session IDs to 0 since we don't support them anymore. michael@0: // We don't set them to NULL to avoid breaking downgrades. michael@0: nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_historyvisits SET session = 0" michael@0: )); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: Database::MigrateV23Up() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: // Recalculate hosts prefixes. michael@0: nsCOMPtr updatePrefixesStmt; michael@0: nsresult rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING( michael@0: "UPDATE moz_hosts SET prefix = ( " HOSTS_PREFIX_PRIORITY_FRAGMENT ") " michael@0: ), getter_AddRefs(updatePrefixesStmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr ps; michael@0: rv = updatePrefixesStmt->ExecuteAsync(nullptr, getter_AddRefs(ps)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: Database::Shutdown() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: MOZ_ASSERT(!mShuttingDown); michael@0: MOZ_ASSERT(!mClosed); michael@0: michael@0: mShuttingDown = true; michael@0: michael@0: mMainThreadStatements.FinalizeStatements(); michael@0: mMainThreadAsyncStatements.FinalizeStatements(); michael@0: michael@0: nsRefPtr< FinalizeStatementCacheProxy > event = michael@0: new FinalizeStatementCacheProxy( michael@0: mAsyncThreadStatements, NS_ISUPPORTS_CAST(nsIObserver*, this) michael@0: ); michael@0: DispatchToAsyncThread(event); michael@0: michael@0: mClosed = true; michael@0: michael@0: nsRefPtr closeListener = michael@0: new ConnectionCloseCallback(); michael@0: (void)mMainConn->AsyncClose(closeListener); michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: //// nsIObserver michael@0: michael@0: NS_IMETHODIMP michael@0: Database::Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (strcmp(aTopic, TOPIC_PROFILE_CHANGE_TEARDOWN) == 0) { michael@0: // Tests simulating shutdown may cause multiple notifications. michael@0: if (mShuttingDown) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr os = services::GetObserverService(); michael@0: NS_ENSURE_STATE(os); michael@0: michael@0: // If shutdown happens in the same mainthread loop as init, observers could michael@0: // handle the places-init-complete notification after xpcom-shutdown, when michael@0: // the connection does not exist anymore. Removing those observers would michael@0: // be less expensive but may cause their RemoveObserver calls to throw. michael@0: // Thus notify the topic now, so they stop listening for it. michael@0: nsCOMPtr e; michael@0: if (NS_SUCCEEDED(os->EnumerateObservers(TOPIC_PLACES_INIT_COMPLETE, michael@0: getter_AddRefs(e))) && e) { michael@0: bool hasMore = false; michael@0: while (NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore) { michael@0: nsCOMPtr supports; michael@0: if (NS_SUCCEEDED(e->GetNext(getter_AddRefs(supports)))) { michael@0: nsCOMPtr observer = do_QueryInterface(supports); michael@0: (void)observer->Observe(observer, TOPIC_PLACES_INIT_COMPLETE, nullptr); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Notify all Places users that we are about to shutdown. michael@0: (void)os->NotifyObservers(nullptr, TOPIC_PLACES_SHUTDOWN, nullptr); michael@0: } michael@0: michael@0: else if (strcmp(aTopic, TOPIC_PROFILE_BEFORE_CHANGE) == 0) { michael@0: // Tests simulating shutdown may cause re-entrance. michael@0: if (mShuttingDown) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Fire internal shutdown notifications. michael@0: nsCOMPtr os = services::GetObserverService(); michael@0: if (os) { michael@0: (void)os->NotifyObservers(nullptr, TOPIC_PLACES_WILL_CLOSE_CONNECTION, nullptr); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: { // Sanity check for missing guids. michael@0: bool haveNullGuids = false; michael@0: nsCOMPtr stmt; michael@0: michael@0: nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT 1 " michael@0: "FROM moz_places " michael@0: "WHERE guid IS NULL " michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = stmt->ExecuteStep(&haveNullGuids); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: MOZ_ASSERT(!haveNullGuids && "Found a page without a GUID!"); michael@0: michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT 1 " michael@0: "FROM moz_bookmarks " michael@0: "WHERE guid IS NULL " michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = stmt->ExecuteStep(&haveNullGuids); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: MOZ_ASSERT(!haveNullGuids && "Found a bookmark without a GUID!"); michael@0: michael@0: rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING( michael@0: "SELECT 1 " michael@0: "FROM moz_favicons " michael@0: "WHERE guid IS NULL " michael@0: ), getter_AddRefs(stmt)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = stmt->ExecuteStep(&haveNullGuids); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: MOZ_ASSERT(!haveNullGuids && "Found a favicon without a GUID!"); michael@0: } michael@0: #endif michael@0: michael@0: // As the last step in the shutdown path, finalize the database handle. michael@0: Shutdown(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace places michael@0: } // namespace mozilla